├── .gitmodules
├── .settings
├── org.eclipse.cdt.codan.core.prefs
├── org.eclipse.cdt.debug.core.prefs
└── org.eclipse.core.resources.prefs
├── .gitignore
├── error.h
├── adc.h
├── util
├── tx_stress.py
├── endurance_test.py
├── amp_sweep.py
├── com_curses.py
├── test.py
├── ber_test.py
├── com_radio_2.py
├── selectivity_test.py
└── com_radio.py
├── .ccsproject
├── targetConfigs
├── readme.txt
└── MSP430FR5994.ccxml
├── .project
├── msp430_uart.h
├── amp.h
├── README.md
├── rbuf.h
├── status.c
├── lfr.h
├── amp.c
├── rbuf.c
├── status.h
├── radio.h
├── pins.h
├── settings.h
├── mcu.h
├── cmd_parser.h
├── tests
└── test_pkt_buf.c
├── pkt_buf.h
├── adc.c
├── settings.c
├── cmd_handler.h
├── pkt_buf.c
├── msp430_uart.c
├── radio.c
├── cmd_parser.c
├── cmd_handler.c
├── lfr.c
├── modem_configs.h
├── lnk_msp430fr5994.cmd
└── mcu.c
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib446x"]
2 | path = lib446x
3 | url = git@github.com:UBNanosatLab/lib446x.git
4 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.cdt.codan.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | inEditor=false
3 | onBuild=false
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .launches/*
2 | Debug/*
3 | *.gch
4 | /Debug/
5 | .vscode/
6 | */__pycache__/
7 | *.json
8 | *.csv
9 |
10 | *.pyc
11 |
12 | .DS_Store
13 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.cdt.debug.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.cdt.debug.core.toggleBreakpointModel=com.ti.ccstudio.debug.CCSBreakpointMarker
3 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.core.resources.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | encoding//Debug/lib446x/subdir_rules.mk=UTF-8
3 | encoding//Debug/lib446x/subdir_vars.mk=UTF-8
4 | encoding//Debug/makefile=UTF-8
5 | encoding//Debug/objects.mk=UTF-8
6 | encoding//Debug/sources.mk=UTF-8
7 | encoding//Debug/subdir_rules.mk=UTF-8
8 | encoding//Debug/subdir_vars.mk=UTF-8
9 |
--------------------------------------------------------------------------------
/error.h:
--------------------------------------------------------------------------------
1 | /*
2 | * error.h
3 | *
4 | * Created on: Jan 23, 2019
5 | * Author: iracigt
6 | */
7 |
8 | #ifndef ERROR_H_
9 | #define ERROR_H_
10 |
11 | // Import all lib446x errors
12 | #include "lib446x/si446x.h"
13 |
14 | #define EOVERFLOW 11
15 | #define EUNDERFLOW 12
16 | #define ECMDINVAL 19
17 | #define ECMDBADSUM 22
18 |
19 |
20 |
21 | #endif /* ERROR_H_ */
22 |
--------------------------------------------------------------------------------
/adc.h:
--------------------------------------------------------------------------------
1 | /*
2 | * adc.h
3 | *
4 | * Created on: Feb 14, 2019
5 | * Author: brian
6 | */
7 |
8 | #ifndef ADC_H_
9 | #define ADC_H_
10 |
11 | #define ADC_CHAN_TEMP (0x1E)
12 | void adc_init();
13 |
14 | int gpio_to_adc(unsigned char pin);
15 | int adc_to_gpio(unsigned char channel);
16 | /*
17 | * adc_read: Accepts an ADC channel ID A0..A16
18 | */
19 | int adc_read(unsigned int chan);
20 |
21 | #endif /* ADC_H_ */
22 |
--------------------------------------------------------------------------------
/util/tx_stress.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import numpy
5 | import time
6 | from com_radio import Radio
7 |
8 | if __name__ == "__main__":
9 |
10 | radio = Radio(sys.argv[1])
11 | num_pkts = int(sys.argv[2])
12 | delay_s = int(sys.argv[3]) / 1000.0
13 |
14 | radio.reset()
15 | time.sleep(0.5)
16 |
17 | for i in range(num_pkts):
18 | print('Sending packet {:03d}...'.format(i))
19 | radio.tx('TEST!' * 51)
20 | time.sleep(delay_s)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.ccsproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/targetConfigs/readme.txt:
--------------------------------------------------------------------------------
1 | The 'targetConfigs' folder contains target-configuration (.ccxml) files, automatically generated based
2 | on the device and connection settings specified in your project on the Properties > General page.
3 |
4 | Please note that in automatic target-configuration management, changes to the project's device and/or
5 | connection settings will either modify an existing or generate a new target-configuration file. Thus,
6 | if you manually edit these auto-generated files, you may need to re-apply your changes. Alternatively,
7 | you may create your own target-configuration file for this project and manage it manually. You can
8 | always switch back to automatic target-configuration management by checking the "Manage the project's
9 | target-configuration automatically" checkbox on the project's Properties > General page.
--------------------------------------------------------------------------------
/targetConfigs/MSP430FR5994.ccxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | LFR
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.cdt.managedbuilder.core.genmakebuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder
15 | full,incremental,
16 |
17 |
18 |
19 |
20 |
21 | com.ti.ccstudio.core.ccsNature
22 | org.eclipse.cdt.core.cnature
23 | org.eclipse.cdt.managedbuilder.core.managedBuildNature
24 | org.eclipse.cdt.core.ccnature
25 | org.eclipse.cdt.managedbuilder.core.ScannerConfigNature
26 |
27 |
28 |
--------------------------------------------------------------------------------
/msp430_uart.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | int uart_init();
20 | void uart_init_pins();
21 | int uart_putc(char c);
22 | int uart_putbuf(char* str, int len);
23 | int uart_available();
24 | char uart_getc();
25 |
--------------------------------------------------------------------------------
/amp.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef AMP_H_
20 | #define AMP_H_
21 |
22 | int set_gate_bias(uint16_t bias_mv);
23 | int set_drain_voltage(uint16_t vdd_mv);
24 | int set_current_limit(uint16_t ilim_ma);
25 |
26 | #endif /* AMP_H_ */
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lfr-firmware
2 | Little Free Radio Firmware
3 |
4 | This repository holds the firmware running on the MSP430FR5994 inside LFR.
5 |
6 | The code here is distributed as a TI Code Composer Studio (Eclipse) project.
7 | Install CCS, clone the repository, then import the project. This allows the
8 | use of the CCS environment for development, building, and source-level
9 | debugging.
10 |
11 | ## License
12 |
13 | ```
14 | Little Free Radio - An Open Source Radio for CubeSats
15 | Copyright (C) 2018 Grant Iraci, Brian Bezanson
16 | A project of the University at Buffalo Nanosatellite Laboratory
17 |
18 | This program is free software: you can redistribute it and/or modify
19 | it under the terms of the GNU General Public License as published by
20 | the Free Software Foundation, either version 3 of the License, or
21 | (at your option) any later version.
22 |
23 | This program is distributed in the hope that it will be useful,
24 | but WITHOUT ANY WARRANTY; without even the implied warranty of
25 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 | GNU General Public License for more details.
27 |
28 | You should have received a copy of the GNU General Public License
29 | along with this program. If not, see .
30 | ```
--------------------------------------------------------------------------------
/rbuf.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #define RBUF_SIZE 512
20 | #include
21 | #include "error.h"
22 |
23 | #ifdef __cplusplus
24 | extern "C" {
25 | #endif
26 |
27 | typedef struct rbuf{
28 | volatile char buffer[RBUF_SIZE];
29 | volatile uint16_t head;
30 | volatile uint16_t tail;
31 | } rbuf;
32 |
33 | void rbuf_init(rbuf* buf);
34 | uint16_t rbuf_size(rbuf* buf);
35 | int rbuf_put(rbuf* buf, char c);
36 | int rbuf_get(rbuf* buf, char* c);
37 |
38 | #ifdef __cplusplus
39 | }
40 | #endif
41 |
--------------------------------------------------------------------------------
/status.c:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #include
20 | #include
21 | #include "status.h"
22 |
23 | static uint32_t status = 0;
24 |
25 | void set_status(uint8_t key, bool val)
26 | {
27 | if (val) {
28 | status |= (1 << key);
29 | } else {
30 | status &= ~(1 << key);
31 | }
32 | }
33 |
34 | bool get_status(uint8_t key)
35 | {
36 | return (status & (1 << key)) ? true : false;
37 | }
38 |
39 | uint32_t get_all_status()
40 | {
41 | return status;
42 | }
43 |
--------------------------------------------------------------------------------
/lfr.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef LFR_H_
20 | #define LFR_H_
21 |
22 | #include "pkt_buf.h"
23 |
24 | /* Global variables */
25 | extern struct si446x_device dev;
26 | extern uint8_t buf[255];
27 | extern volatile bool do_pong;
28 | extern struct pkt_buf tx_queue;
29 |
30 | int reload_config();
31 | int pre_transmit();
32 | int post_transmit();
33 | int reset_si446x();
34 | void rx_cb(struct si446x_device *dev, int err, int len, uint8_t *data);
35 | void tx_cb(struct si446x_device *dev, int err);
36 | int send_w_retry(int len, uint8_t *buf);
37 |
38 | #endif /* LFR_H_ */
39 |
--------------------------------------------------------------------------------
/amp.c:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #include
20 | #include
21 |
22 | #include "mcu.h"
23 | #include "error.h"
24 | #include "pins.h"
25 |
26 | int set_gate_bias(uint16_t bias_mv)
27 | {
28 | // Already 1 mV / LSB
29 | return set_dac_output(GATE_CHAN, bias_mv);
30 | }
31 |
32 | int set_drain_voltage(uint16_t vdd_mv)
33 | {
34 | // 97 counts = 1V
35 | // So 99 counts = 1024 mv
36 | return set_dac_output(VSET_CHAN, (uint16_t)((vdd_mv * (uint32_t)99) >> 10));
37 | }
38 |
39 | int set_current_limit(uint16_t ilim_ma)
40 | {
41 | // 2mA/LSB, up to 1000 (2A)
42 | return set_dac_output(ISET_CHAN, ilim_ma >> 1);
43 | }
44 |
--------------------------------------------------------------------------------
/rbuf.c:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #include "rbuf.h"
20 | #include "error.h"
21 |
22 | int rbuf_put(rbuf* buf, char c){
23 | static uint16_t next;
24 | next=(buf->head+1)%RBUF_SIZE;
25 | if(next != buf->tail){
26 | buf->buffer[buf->head] = c;
27 | buf->head=next;
28 | return 0;
29 | } else {
30 | return -EOVERFLOW;
31 | }
32 | }
33 |
34 | int rbuf_get(rbuf* buf, char* c){
35 | if(buf->head != buf->tail){
36 | *c = buf->buffer[buf->tail];
37 | buf->tail = (buf->tail+1)%RBUF_SIZE;
38 | return 0;
39 | } else {
40 | return -EUNDERFLOW;
41 | }
42 | }
43 |
44 | uint16_t rbuf_size(rbuf* buf){
45 | return ((uint16_t)(buf->head-buf->tail))%RBUF_SIZE;
46 | }
47 |
48 | void rbuf_init(rbuf* buf){
49 | buf->head=buf->tail=0;
50 | }
51 |
--------------------------------------------------------------------------------
/status.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef STATUS_H
20 | #define STATUS_H
21 |
22 | #include
23 | #include
24 |
25 | // TODO: Mostly unimplemented
26 |
27 | #define STATUS_RESET 0
28 | //#define STATUS_CMDREPLY 1
29 | //#define STATUS_CMDERR 2
30 |
31 | //#define STATUS_INTERNALERR 3
32 | //#define STATUS_RFICBUG 4
33 | //#define STATUS_CRCMISS 5
34 | //#define STATUS_PATIMEOUT 6
35 | //#define STATUS_PAOVERCUR 7
36 |
37 | #define STATUS_TXBUSY 8
38 | //#define STATUS_TXFULL 9
39 | //#define STATUS_TXEMPTY 10
40 | //#define STATUS_RXBUSY 11
41 | //#define STATUS_RXFULL 12
42 | //#define STATUS_RXNOTEMPTY 13
43 |
44 |
45 | void set_status(uint8_t key, bool val);
46 | bool get_status(uint8_t key);
47 | uint32_t get_all_status();
48 |
49 | #endif
50 |
--------------------------------------------------------------------------------
/radio.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef RADIO_H
20 | #define RADIO_H
21 |
22 | #include
23 | #include
24 |
25 | #define DATA_1K_DEV_0K25 0x00
26 | #define DATA_1K_DEV_0K5 0x01
27 | #define DATA_2K5_DEV_0K625 0x02
28 | #define DATA_2K5_DEV_1K25 0x03
29 | #define DATA_5K_DEV_1K25 0x04
30 | #define DATA_5K_DEV_2K5 0x05
31 | #define DATA_10K_DEV_2K5 0x06
32 | #define DATA_10K_DEV_5K 0x07
33 | #define DATA_25K_DEV_6K25 0x08
34 | #define DATA_25K_DEV_12K5 0x09
35 | #define DATA_50K_DEV_12K5 0x0A
36 | #define DATA_50K_DEV_25K 0x0B
37 | #define DATA_100K_DEV_25K 0x0C
38 | #define DATA_100K_DEV_50K 0x0D
39 | #define DATA_250K_DEV_62K5 0x0E
40 | #define DATA_250K_DEV_125K 0x0F
41 |
42 | int set_frequency(uint32_t freq);
43 | int set_modem_config(uint8_t cfg);
44 |
45 | #endif
46 |
--------------------------------------------------------------------------------
/pins.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef PINS_H_
20 | #define PINS_H_
21 |
22 | // Pin definitions for Rev. 2B
23 | #define NSEL_PIN 0x37
24 | #define SDN_PIN 0x45
25 | #define INT_PIN 0x53
26 | #define GPIO0 0x20
27 | #define GPIO1 0x10
28 | #define GPIO2 0x36
29 | #define GPIO3 0x35
30 | #define TX_ACT_PIN 0x72
31 | #define PA_TEMP_PIN 0x40
32 | #define PA_TEMP_ACHAN 8 //ADC channel 8
33 | #define PA_IMON_PIN 0x42
34 | #define PA_IMON_ACHAN 10 //ADC channel 10
35 | #define PA_VMON_PIN 0x14
36 | #define PA_VMON_ACHAN 4 //ADC & comparator channel 4
37 | #define PA_VSET_PIN 0x15
38 | #define PA_VSET_ACHAN 5 //ADC & comparator channel 5
39 | #define PA_PWR_EN_PIN 0x43
40 | #define PA_PGOOD_PIN 0x74
41 | #define DAC_nPWR_PIN 0x73
42 | #define SDA_PIN 0x70
43 | #define SCL_PIN 0x71
44 |
45 | #define GATE_CHAN 0
46 | #define TCXO_CHAN 1
47 | #define VSET_CHAN 2
48 | #define ISET_CHAN 3
49 |
50 | #define XTAL_FREQ 26000000L
51 | #define OSC_TYPE OPT_TCXO
52 |
53 | #endif /* PINS_H_ */
54 |
--------------------------------------------------------------------------------
/settings.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef SETTINGS_H
20 | #define SETTINGS_H
21 |
22 | #include
23 | #include
24 |
25 | #define SETTINGS_VER 0x02
26 |
27 | #define FLAG_CRC_CHECK 0x0001
28 | #define FLAG_WHITEN 0x0002
29 |
30 | #define FLAG_MOD_MASK 0x000C
31 | #define FLAG_MOD_GFSK 0x0000
32 | #define FLAG_MOD_FSK 0x0004
33 | #define FLAG_MOD_CW 0x0008
34 |
35 |
36 | struct lfr_settings {
37 | uint32_t freq;
38 | uint8_t modem_config;
39 | uint16_t tcxo_vpull;
40 | uint16_t tx_gate_bias;
41 | uint16_t tx_vdd;
42 | uint16_t pa_ilimit;
43 | uint16_t tx_vdd_delay;
44 | uint16_t flags;
45 | uint8_t callsign[8];
46 | };
47 |
48 | struct lfr_board_info {
49 | const char *sw_ver;
50 | uint16_t serial_no;
51 | };
52 |
53 |
54 | // Must call settings_load_xxx() before use!!
55 | extern struct lfr_settings settings;
56 | extern const struct lfr_board_info board_info;
57 |
58 | int settings_load_default();
59 | int settings_load_saved();
60 | int settings_save();
61 |
62 | uint64_t settings_get_silicon_id();
63 |
64 |
65 |
66 | #endif /* SETTINGS_H */
67 |
--------------------------------------------------------------------------------
/mcu.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef MCU_H
20 | #define MCU_H
21 |
22 | #include
23 | #include
24 |
25 | #define HIGH 0x1
26 | #define LOW 0x0
27 |
28 | #define INPUT 0x0
29 | #define OUTPUT 0x1
30 | #define INPUT_PULLUP 0x30
31 | #define INPUT_PULLDOWN 0x20
32 | #define INPUT_ANALOG 0x40
33 |
34 | #define RISING 0x00
35 | #define FALLING 0x01
36 |
37 | #define USE_HFXT
38 |
39 | void mcu_init();
40 | void mcu_reset();
41 | void wdt_feed();
42 |
43 | void i2c_init();
44 | int i2c_write(unsigned char slave_addr, unsigned char *buf, unsigned char len);
45 |
46 | void spi_init();
47 | void spi_write_byte(unsigned char b);
48 | void spi_write_data(int len, const unsigned char *data);
49 | void spi_read_data(int len, unsigned char *data);
50 |
51 | void gpio_config(int pin, int mode);
52 | void gpio_write(int pin, int value);
53 | unsigned char gpio_read(int pin);
54 | int enable_pin_interrupt(int pin, int edge);
55 | int disable_pin_interrupt(int pin);
56 |
57 | void delay_micros(unsigned long micros);
58 |
59 | /**
60 | * @brief Set the output voltage of a DAC channel, in mV
61 | */
62 | int set_dac_output(uint8_t chan, uint16_t mv);
63 |
64 | #endif
65 |
--------------------------------------------------------------------------------
/util/endurance_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import numpy
5 | import time
6 | from com_radio import Radio
7 |
8 | radio = Radio(sys.argv[1])
9 | time.sleep(0.5)
10 |
11 | large_pkt_delay = 0.250
12 | small_pkt_delay = 0.040
13 |
14 | num_iterations = 0
15 |
16 | while True:
17 |
18 | num_iterations += 1
19 | print('Starting stress iteration', num_iterations)
20 |
21 | # Send some NOPs
22 | for i in range(1000):
23 | print('Sending NOP {:03d}...'.format(i))
24 | radio.nop()
25 |
26 | # Send a bunch of big packets
27 | # Fill the buffer
28 | for i in range(100):
29 | print('Sending big packet {:03d}...'.format(i))
30 | radio.tx('KC2QOL {:03d} '.format(i).ljust(255, 'x'))
31 |
32 | time.sleep(large_pkt_delay * 50) # Let about half send
33 |
34 | # Send some more big packets
35 | # Fill the buffer
36 | for i in range(50):
37 | print('Sending big packet {:03d}...'.format(i))
38 | radio.tx('KC2QOL {:03d} '.format(i).ljust(255, 'x'))
39 |
40 | time.sleep(large_pkt_delay * 100) # Let all send
41 |
42 | # Send a bunch of small packets
43 | # Fill the buffer
44 | for i in range(200):
45 | print('Sending small packet {:03d}...'.format(i))
46 | radio.tx('KC2QOL {:03d} '.format(i).ljust(25, 'x'))
47 |
48 | time.sleep(small_pkt_delay * 100) # Let about half send
49 |
50 | # Send some more big packets
51 | # Fill the buffer
52 | for i in range(50):
53 | print('Sending big packet {:03d}...'.format(i))
54 | radio.tx('KC2QOL {:03d} '.format(i).ljust(255, 'x'))
55 |
56 | time.sleep(large_pkt_delay * 100) # Let all send
57 |
58 | # Send a ton of small packets
59 | for i in range(1000):
60 | print('Sending small packet {:03d}...'.format(i))
61 | radio.tx('KC2QOL {:03d} '.format(i).ljust(25, 'x'))
62 |
63 | time.sleep(small_pkt_delay * 1000) # Let all send
64 |
65 | # Wait a while before repeating
66 | print('Idling for 15 seconds')
67 | time.sleep(15)
68 |
69 |
70 |
--------------------------------------------------------------------------------
/util/amp_sweep.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import numpy
5 | import serial
6 | import time
7 | from com_radio import Radio
8 |
9 | def ad8318_mv2dbm(mv):
10 | return (mv / -24.5) + 25.0
11 |
12 | def ina169_mv2ma(mv):
13 | # 1 V per A
14 | return mv
15 |
16 | def dbm2mw(dbm):
17 | return 10**(dbm/10.0)
18 |
19 | if __name__ == "__main__":
20 |
21 | num_samples = 5
22 | atten_db = 40.0
23 |
24 | drain_voltages = numpy.linspace(5000, 10000, num=11)
25 | gate_voltages = numpy.linspace(2800, 3300, num=6)
26 |
27 | vbatt = 16.000
28 |
29 | radio = Radio(sys.argv[1])
30 | arduino = serial.Serial(sys.argv[2], 115200)
31 |
32 | cfg = radio.get_cfg()
33 | orig_cfg = dict(cfg)
34 |
35 | for drain_mv in drain_voltages:
36 | for gate_mv in gate_voltages:
37 |
38 | cfg['tx_vdd'] = int(drain_mv)
39 | cfg['tx_gate_bias'] = int(gate_mv)
40 | radio.set_cfg(cfg)
41 |
42 | rf_power = numpy.zeros(num_samples)
43 | amp_power = numpy.zeros(num_samples)
44 |
45 | for i in range(num_samples):
46 | radio.tx('TEST!' * 25)
47 | time.sleep(0.050)
48 | arduino.write(b'r')
49 | fields = arduino.readline().decode('utf-8').split(',')
50 | rf_power[i] = ad8318_mv2dbm(int(fields[0])) + atten_db
51 | amp_power[i] = ina169_mv2ma(int(fields[2])) * vbatt
52 | time.sleep(0.100)
53 |
54 | print("{:.0f},{:.0f},{:.1f},{:.0f}".format(drain_mv, gate_mv, numpy.median(rf_power), numpy.median(amp_power)))
55 |
56 | radio.set_cfg(orig_cfg)
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | # // Arduino (Teensy) Code
75 | # void setup()
76 | # {
77 | # Serial.begin(115200);
78 | # analogReadResolution(16);
79 | # }
80 | #
81 | # void loop()
82 | # {
83 | # if (Serial.available() > 0) {
84 | # char c = Serial.read();
85 | #
86 | # if (c == 'r') {
87 | # int ad8318_mv = map(analogRead(0), 0, 65535, 0, 3300);
88 | # int ina169_mv = map(analogRead(1), 0, 65535, 0, 3300);
89 | # Serial.print(ad8318_mv);
90 | # Serial.print(",mV,");
91 | # Serial.print(ina169_mv);
92 | # Serial.println(",mV");
93 | # }
94 | # }
95 | # }
--------------------------------------------------------------------------------
/cmd_parser.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef _CMD_PARSER_H
20 | #define _CMD_PARSER_H
21 |
22 | #include
23 | #include
24 | #define MAX_PAYLOAD_LEN 255
25 |
26 | /* sync word */
27 | #define SYNCWORD_H 0xbe
28 | #define SYNCWORD_L 0xef
29 |
30 | /* commands */
31 | /* System Group */
32 | #define CMD_NOP 0x00
33 | #define CMD_RESET 0x01
34 | //#define CMD_UPTIME 0x02
35 |
36 | /* Data Group */
37 | #define CMD_TXDATA 0x10
38 | #define CMD_RXDATA 0x11
39 | #define CMD_TX_ABORT 0x12
40 | #define CMD_TX_PSR 0x13
41 |
42 | /* Configuration Group */
43 | #define CMD_GET_CFG 0x20
44 | #define CMD_SET_CFG 0x21
45 | #define CMD_SAVE_CFG 0x22
46 | #define CMD_CFG_DEFAULT 0x23
47 | #define CMD_SET_FREQ 0x24
48 | #define CMD_READ_TXPWR 0x25
49 | #define CMD_SET_TXPWR 0x26
50 |
51 | /* Status Group */
52 | //#define CMD_GET_STATUS 0x30
53 | //#define CMD_CLEAR_STATUS 0x31
54 | #define CMD_GET_QUEUE_DEPTH 0x32
55 | #define CMD_GET_TEMPS 0x33
56 |
57 | /* Peripheral Group */
58 | //#define CMD_GPIO_WRITE 0x40
59 |
60 | /* Error / Internal Use Group */
61 | #define CMD_INTERNALERR 0x7E
62 | #define CMD_REPLYERR 0x7F
63 |
64 | /* Reply bit */
65 | #define CMD_REPLY 0x80
66 |
67 |
68 | #ifdef __cplusplus
69 | extern "C" {
70 | #endif
71 |
72 | void parse_char(uint8_t c);
73 | void send_reply_to_host();
74 | void internal_error(uint8_t code);
75 | void reply_cmd_error(uint8_t code);
76 | void reply(uint8_t cmd, int len, uint8_t *payload);
77 |
78 | #ifdef __cplusplus
79 | }
80 | #endif
81 |
82 | #endif
83 |
--------------------------------------------------------------------------------
/tests/test_pkt_buf.c:
--------------------------------------------------------------------------------
1 | #if 0
2 |
3 | #include
4 | #include
5 |
6 | #include "../pkt_buf.h"
7 |
8 | void assert_fail(const char *file, int line, const char *func, const char *test, const char *msg)
9 | {
10 | printf("%s:%d: Assertion %s failed in %s: %s\n", file, line, test, func, msg);
11 | }
12 |
13 | #define ASSERT(x, msg) {if (!(x)){ assert_fail(__FILE__, __LINE__, __func__, #x, msg); return 1;}}
14 |
15 | uint16_t chksum(uint8_t *data, int len) {
16 | uint8_t lsb = 0, msb = 0;
17 | for (int i = 0; i < len; i++) {
18 | msb += data[i];
19 | lsb += msb;
20 | }
21 | return ((uint16_t) msb<<8) | (uint16_t)lsb;
22 | }
23 |
24 | int test_stress()
25 | {
26 | const int BUF_SIZE = 16384;
27 | const int PKT_SIZE = 255;
28 |
29 | uint8_t mem[BUF_SIZE + 2*sizeof(uint64_t)] = {0};
30 | uint64_t *guard_front = (uint64_t *)mem;
31 | uint8_t *backing_buf = mem + sizeof(uint64_t);
32 | uint64_t *guard_back = (uint64_t *)(mem + sizeof(uint64_t) + BUF_SIZE);
33 |
34 | struct pkt_buf buf;
35 |
36 | int err = 0;
37 |
38 | *guard_front = 0xDEADBEEF;
39 | *guard_back = 0xFEEBDAED;
40 |
41 | err = pkt_buf_init(&buf, BUF_SIZE, backing_buf);
42 | ASSERT(err == 0, "Failed to init buffer");
43 |
44 | uint8_t msg[PKT_SIZE];
45 |
46 | for (int i = 0; i < PKT_SIZE; i++) {
47 | msg[i] = (uint8_t)(i & 0xFF);
48 | }
49 |
50 | const uint16_t correct = chksum(msg, PKT_SIZE);
51 |
52 | while (!pkt_buf_enqueue(&buf, PKT_SIZE, msg));
53 |
54 | for (int i = 0; i < 100; i++) {
55 | uint8_t data[PKT_SIZE] = {0};
56 | int len = PKT_SIZE;
57 | err = pkt_buf_dequeue(&buf, &len, data);
58 | ASSERT(err == 0, "Failed to dequeue");
59 |
60 | uint16_t chk = chksum(data, len);
61 | ASSERT(chk == correct, "Checksum mismatch");
62 |
63 | pkt_buf_enqueue(&buf, PKT_SIZE, msg);
64 |
65 | ASSERT(*guard_front == 0xDEADBEEF, "Front guard corrupted");
66 | ASSERT(*guard_back == 0xFEEBDAED, "Back guard corrupted");
67 | }
68 |
69 | uint8_t data[PKT_SIZE] = {0};
70 | int len = PKT_SIZE;
71 | while (!pkt_buf_dequeue(&buf, &len, data)) {
72 | uint16_t chk = chksum(data, len);
73 | ASSERT(chk == correct, "Checksum mismatch");
74 | len = PKT_SIZE;
75 | }
76 |
77 | ASSERT(*guard_front == 0xDEADBEEF, "Front guard corrupted");
78 | ASSERT(*guard_back == 0xFEEBDAED, "Back guard corrupted");
79 |
80 | return 0;
81 | }
82 |
83 |
84 | int main(int argc, char const *argv[])
85 | {
86 | return test_stress();
87 | }
88 |
89 | #endif
90 |
--------------------------------------------------------------------------------
/pkt_buf.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef PKT_BUF_H
20 | #define PKT_BUF_H
21 |
22 | #include
23 |
24 | struct pkt_buf
25 | {
26 | uint16_t capacity; // Size of backing buffer
27 | uint8_t *head; // Pointer to first unallocated byte
28 | uint8_t *tail; // Pointer to first allocated byte
29 | uint8_t *base; // Pointer to base of backing buffer
30 | uint16_t depth; // Number of packets in the buffer
31 | };
32 |
33 | /**
34 | * Initialize a new packet buffer structure
35 | * @param buf the buffer structure to initialize
36 | * @param capacity the number of bytes in the backing buffer
37 | * @param buffer pointer to the beginning of the backing buffer
38 | * @return negative error code or 0 for success
39 | */
40 | int pkt_buf_init(struct pkt_buf *buf, int capacity, uint8_t *buffer);
41 |
42 |
43 | /**
44 | * Add a packet to the buffer
45 | * @param buf the buffer structure
46 | * @param len the number of bytes in the packet
47 | * @param data pointer to packet data
48 | * @return negative error code or 0 for success
49 | */
50 | int pkt_buf_enqueue(struct pkt_buf *buf, int len, uint8_t *data);
51 |
52 | /**
53 | * Remove a packet from the buffer
54 | * @param buf the buffer structure
55 | * @param len maximium size packet to dequeue, overwritten with
56 | * actual packet size
57 | * @param data pointer to buffer to fill with packet data
58 | * @return negative error code or 0 for success
59 | */
60 | int pkt_buf_dequeue(struct pkt_buf *buf, int *len, uint8_t *data);
61 |
62 | /**
63 | * Clear the buffer, dropping all packets
64 | * @param buf the buffer structure
65 | * @return negative error code or 0 for success
66 | */
67 | void pkt_buf_clear(struct pkt_buf *buf);
68 |
69 | /**
70 | * Get the number of packets in the buffer
71 | * @param buf the buffer structure
72 | * @return number of packets
73 | */
74 | int pkt_buf_depth(struct pkt_buf *buf);
75 |
76 | /**
77 | * Get the number of used bytes in the buffer
78 | * @param buf the buffer structure
79 | * @return number of bytes
80 | */
81 | int pkt_buf_size(struct pkt_buf *buf);
82 |
83 | #endif
84 |
--------------------------------------------------------------------------------
/adc.c:
--------------------------------------------------------------------------------
1 | /*
2 | * adc.c
3 | *
4 | * Created on: Feb 14, 2019
5 | * Author: brian
6 | */
7 | #include
8 | #include "adc.h"
9 | #include "error.h"
10 | #include "mcu.h"
11 |
12 | const unsigned char analog_pins[] = {
13 | 0x10,
14 | 0x11,
15 | 0x12,
16 | 0x13,
17 | 0x14,
18 | 0x15,
19 | 0x23,
20 | 0x24,
21 | 0x40,
22 | 0x41,
23 | 0x42,
24 | 0x43,
25 | 0x30,
26 | 0x31,
27 | 0x32,
28 | 0x33,
29 | 0x74
30 | };
31 |
32 |
33 | void adc_init(){
34 | // By default, REFMSTR=1 => REFCTL is used to configure the internal reference
35 | while(REFCTL0 & REFGENBUSY); // If ref generator busy, WAIT
36 | //REFCTL0 |= REFVSEL_0 | REFON; // Select internal ref = 1.2V
37 | // Internal Reference ON
38 | REFCTL0 |= REFVSEL_2 | REFON; //Internal 2.5V reference
39 |
40 | // Configure ADC12
41 | ADC12CTL0 = ADC12SHT0_4 | ADC12ON;
42 | ADC12CTL1 = ADC12SHP; // ADCCLK = MODOSC; sampling timer
43 | ADC12CTL2 |= ADC12RES_2; // 12-bit conversion results
44 | ADC12CTL3 |= ADC12TCMAP; // Connect internal temperature sensor to channel 30d/1Eh
45 | //ADC12IER0 |= ADC12IE0; // Enable ADC conv complete interrupt
46 | ADC12MCTL0 |= ADC12VRSEL_1; //Select the internal reference as VREF
47 |
48 | while(!(REFCTL0 & REFGENRDY)); // Wait for reference generator
49 | // to settle
50 | }
51 |
52 | /*
53 | * Search the ADC-to-GPIO map for the specified pin. Pins not muxable to the ADC will return -EINVAL
54 | */
55 | int gpio_to_adc(unsigned char iopin){
56 | unsigned char i;
57 | for(i=0; i.
17 | */
18 |
19 | #include
20 | #include
21 |
22 | #include "settings.h"
23 | #include "radio.h"
24 |
25 | #define DEFAULT_SETTINGS {\
26 | .freq = 432500000L, \
27 | .modem_config = DATA_10K_DEV_2K5, \
28 | .tcxo_vpull = 1256, \
29 | .tx_gate_bias = 500, /* 0.5V bias is enough to see in testing, but very very low output power */ \
30 | .tx_vdd = 5000, /* 5V will wake the PA up, but dissipate little heat even with a lot of gate bias */ \
31 | .pa_ilimit = 1000, /* 1A is a bit more than the RA07H4047M draws at Vdd=5V, Vgg=3.5V, Pin=13dBm */ \
32 | .tx_vdd_delay = 5000, /* ? 2ms is plenty for the example 5V/1A values */ \
33 | .flags = FLAG_MOD_GFSK | FLAG_CRC_CHECK,\
34 | .callsign = "NOCALL "\
35 | };
36 |
37 |
38 | const char sw_ver[] = "v19.02.0rc1 " __DATE__ " " __TIME__;
39 |
40 | const struct lfr_board_info board_info = {
41 | // .fw_git_hash = 0,
42 | .sw_ver = sw_ver,
43 | .serial_no = 0x0000
44 | };
45 |
46 | const struct lfr_settings default_settings = DEFAULT_SETTINGS;
47 |
48 | struct lfr_settings saved_settings __attribute__((persistent)) = DEFAULT_SETTINGS;
49 |
50 | // Must call settings_load_xxx() before use!!
51 | struct lfr_settings settings = {0};
52 |
53 | int settings_load_default()
54 | {
55 | unsigned short state = __get_interrupt_state();
56 | __disable_interrupt();
57 | memcpy(&settings, &default_settings, sizeof(settings));
58 | __set_interrupt_state(state);
59 | return 0;
60 | }
61 |
62 | int settings_load_saved()
63 | {
64 | unsigned short state = __get_interrupt_state();
65 | __disable_interrupt();
66 | memcpy(&settings, &saved_settings, sizeof(settings));
67 | __set_interrupt_state(state);
68 | return 0;
69 | }
70 |
71 | int settings_save()
72 | {
73 | unsigned short state = __get_interrupt_state();
74 | __disable_interrupt();
75 | memcpy(&saved_settings, &settings, sizeof(settings));
76 | __set_interrupt_state(state);
77 | return 0;
78 | }
79 |
80 | // TODO: TEST THIS!!
81 | uint64_t settings_get_silicon_id()
82 | {
83 | #if defined (__MSP430FR5994__)
84 | return *(uint64_t *)(0x00001A0A);
85 | #else
86 | #warning "Silicon ID not available on this chipset!"
87 | return 0L;
88 | #endif
89 | }
90 |
91 |
92 |
--------------------------------------------------------------------------------
/util/test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from com_radio import Radio
4 | import unittest
5 | import sys
6 | import time
7 | import timeout_decorator
8 | import random
9 |
10 | class Communication_Tests(unittest.TestCase):
11 |
12 | def setUp(self):
13 | self.radio_uart = sys.argv[1]
14 | self.radio = Radio(self.radio_uart)
15 | self.radio.flush_serial()
16 | pass
17 |
18 | @timeout_decorator.timeout(2)
19 | def tearDown(self):
20 | self.radio.cfg_default()
21 | self.radio.save_cfg()
22 | self.radio.reset()
23 |
24 | @timeout_decorator.timeout(5)
25 | def test_reset(self):
26 | self.radio.reset()
27 |
28 | @timeout_decorator.timeout(3)
29 | def test_nop(self):
30 | self.radio.nop()
31 |
32 | @timeout_decorator.timeout(3)
33 | def test_get_cfg(self):
34 | self.radio.get_cfg()
35 |
36 | @timeout_decorator.timeout(3)
37 | def test_set_cfg(self):
38 | test_callsign = 'TESTER{:02d}'.format(random.randrange(100))
39 | cfg = self.radio.get_cfg()
40 | cfg['callsign'] = test_callsign
41 | self.radio.set_cfg(cfg)
42 | new_cfg = self.radio.get_cfg()
43 | self.assertEqual(cfg, new_cfg)
44 |
45 | @timeout_decorator.timeout(3)
46 | def test_default_cfg(self):
47 | test_callsign = 'TESTER{:02d}'.format(random.randrange(100))
48 | cfg = self.radio.get_cfg()
49 | cfg['callsign'] = test_callsign
50 | self.radio.set_cfg(cfg)
51 | self.radio.cfg_default()
52 | new_cfg = self.radio.get_cfg()
53 | self.assertNotEqual(cfg, new_cfg)
54 |
55 | @timeout_decorator.timeout(3)
56 | def test_save_cfg(self):
57 | test_callsign = 'TESTER{:02d}'.format(random.randrange(100))
58 | cfg = self.radio.get_cfg()
59 | cfg['callsign'] = test_callsign
60 |
61 | self.radio.set_cfg(cfg)
62 | self.radio.reset()
63 | new_cfg = self.radio.get_cfg()
64 | self.assertNotEqual(cfg, new_cfg)
65 |
66 | self.radio.set_cfg(cfg)
67 | self.radio.save_cfg()
68 | self.radio.reset()
69 | new_cfg = self.radio.get_cfg()
70 | self.assertEqual(cfg, new_cfg)
71 |
72 | @timeout_decorator.timeout(3)
73 | def test_set_freq(self):
74 | test_freq = random.randrange(430000000, 440000000)
75 | self.radio.set_freq(test_freq)
76 | cfg = self.radio.get_cfg()
77 | self.assertEqual(cfg['freq'], test_freq)
78 |
79 | @timeout_decorator.timeout(30)
80 | def test_set_txpwr(self):
81 | test_pwr = random.randrange(0x001, 0xFFF)
82 | self.radio.set_txpwr(test_pwr)
83 | cfg = self.radio.get_cfg()
84 | self.assertEqual(cfg['tx_gate_bias'], test_pwr)
85 |
86 |
87 |
88 |
89 | def suite(tests=None):
90 | suite = unittest.TestSuite()
91 | if tests:
92 | for test in tests:
93 | suite.addTest(Communication_Tests(test))
94 | else:
95 | suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(Communication_Tests))
96 |
97 | return suite
98 |
99 | if __name__ == '__main__':
100 | if len(sys.argv) < 2:
101 | print('Usage: ' + sys.argv[0] + '/dev/')
102 | sys.exit(1)
103 |
104 | tests = None
105 |
106 | if len(sys.argv) > 2:
107 | tests = sys.argv[2:]
108 |
109 | runner = unittest.TextTestRunner()
110 | runner.run(suite(tests))
111 |
--------------------------------------------------------------------------------
/cmd_handler.h:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #ifndef CMD_HANDLER_H
20 | #define CMD_HANDLER_H
21 |
22 | /**
23 | * Send reply character to the flight computer
24 | * @param c the character to send
25 | */
26 | int host_reply_putc(uint8_t c);
27 |
28 | /**
29 | * No-OPeration
30 | * Replies with success always
31 | */
32 | void cmd_nop();
33 |
34 | /**
35 | * Reset
36 | * Forces a reset of the MCU
37 | */
38 | void cmd_reset();
39 |
40 | /**
41 | * Get transmit power
42 | * Returns the transmit power level (in arbitrary units)
43 | * TODO: Use dBm?
44 | */
45 | void cmd_get_txpwr();
46 |
47 | /**
48 | * Set transmit power
49 | * Sets the transmitter power level (in arbitrary units)
50 | * TODO: Use dBm?
51 | * @param pwr the transmitter power level (gate bias)
52 | */
53 | void cmd_set_txpwr(uint16_t pwr);
54 |
55 | /**
56 | * Transmit the provided data
57 | * @param len the length of the data in bytes
58 | * @param data pointer to the data
59 | */
60 | void cmd_tx_data(int len, uint8_t *data);
61 |
62 | /**
63 | * Set configuration
64 | * @param len the length of the cfg data in bytes
65 | * @param data pointer to the cfg data
66 | */
67 | void cmd_set_cfg(int len, uint8_t *data);
68 |
69 | /**
70 | * Get configuration
71 | * Returns the configuration data
72 | */
73 | void cmd_get_cfg();
74 |
75 | /**
76 | * Save configuration
77 | * Writes the current configuration data to non-volatile storage
78 | */
79 | void cmd_save_cfg();
80 |
81 | /**
82 | * Load default configuration
83 | * Loads the default (factory) configuration into the volatile settings
84 | */
85 | void cmd_cfg_default();
86 |
87 |
88 | /**
89 | * Set frequency
90 | * Changes the frequency
91 | * @param freq desired frequency in Hz
92 | */
93 | void cmd_set_freq(uint32_t freq);
94 |
95 | /**
96 | * Abort TX
97 | * Immediately kills the transmitter and returns to normal receive mode
98 | */
99 | void cmd_abort_tx();
100 |
101 | /**
102 | * Transmit psuedo-random sequence
103 | * Transmits a psuedo-random sequence
104 | */
105 | void cmd_tx_psr();
106 |
107 | /**
108 | * Get received packet
109 | * Returns the next packet in the receive queue
110 | */
111 | void cmd_rx_data();
112 |
113 | /**
114 | * Get the depth of the transmit queue
115 | * Returns the (16-bit) depth of the queue
116 | */
117 | void cmd_get_queue_depth();
118 |
119 | /**
120 | * Get the current temperatures
121 | * Returns two signed 16-bit integers representing PA temperature and MCU temperature in 0.01C units
122 | */
123 | void cmd_get_temps();
124 |
125 | /**
126 | * Send an error back
127 | * @param err the (positive) error code
128 | */
129 | void cmd_err(int err);
130 |
131 | #endif
132 |
--------------------------------------------------------------------------------
/pkt_buf.c:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #include
20 |
21 | #include "pkt_buf.h"
22 | #include "error.h"
23 |
24 | int pkt_buf_init(struct pkt_buf *buf, int capacity, uint8_t *buffer)
25 | {
26 | if (capacity <= 0 || capacity > 0xFFFF) {
27 | return -EINVAL;
28 | }
29 |
30 | buf->capacity = capacity;
31 | buf->base = buffer;
32 | buf->head = buffer;
33 | buf->tail = buffer;
34 | buf->depth = 0;
35 |
36 | return ESUCCESS;
37 | }
38 |
39 | int pkt_buf_enqueue(struct pkt_buf *buf, int len, uint8_t *data)
40 | {
41 | if (len <= 0 || len > 0xFFFF) {
42 | return -EINVAL;
43 | }
44 |
45 | int avail = buf->capacity - pkt_buf_size(buf);
46 | if (len > (avail - 2)) {
47 | return -EOVERFLOW;
48 | }
49 |
50 | // Can we fit the entire packet in one contiguous chunk?
51 | int rem = buf->capacity - (buf->head - buf->base);
52 | if (rem < len + 2) {
53 | // Split the packet and wrap around
54 |
55 | // MSB of length
56 | *buf->head = len >> 8;
57 | rem--;
58 | if (rem > 0) {
59 | buf->head++;
60 | } else {
61 | buf->head = buf->base;
62 | rem = buf->capacity;
63 | }
64 |
65 | // LSB of length
66 | *buf->head = len & 0xFF;
67 | rem--;
68 | if (rem > 0) {
69 | buf->head++;
70 | } else {
71 | buf->head = buf->base;
72 | rem = buf->capacity;
73 | }
74 |
75 | if (rem > len) {
76 | memcpy(buf->head, data, len);
77 | buf->head += len;
78 | } else {
79 | memcpy(buf->head, data, rem);
80 | buf->head = buf->base;
81 | memcpy(buf->head, data + rem, len - rem);
82 | buf->head += len - rem;
83 | }
84 | } else {
85 | // MSB of length
86 | *buf->head = len >> 8;
87 | // LSB of length
88 | *(buf->head+1) = len & 0xFF;
89 | buf->head += 2;
90 | // Data
91 | memcpy(buf->head, data, len);
92 | buf->head += len;
93 | if (buf->head == buf->base + buf->capacity) {
94 | buf->head = buf->base;
95 | }
96 | }
97 |
98 | buf->depth++;
99 |
100 | return ESUCCESS;
101 | }
102 |
103 | int pkt_buf_dequeue(struct pkt_buf *buf, int *len, uint8_t *data)
104 | {
105 | if (*len <= 0) {
106 | return -EINVAL;
107 | }
108 |
109 | if (pkt_buf_size(buf) == 0) {
110 | return -EUNDERFLOW;
111 | }
112 |
113 | uint8_t *old_tail = buf->tail;
114 |
115 | // Did we fit the entire packet in one contiguous chunk?
116 | uint16_t rem = buf->capacity - (buf->tail - buf->base);
117 |
118 | uint16_t pkt_len = (*buf->tail) << 8;
119 | rem--;
120 |
121 | if (rem > 0) {
122 | buf->tail++;
123 | } else {
124 | buf->tail = buf->base;
125 | rem = buf->capacity;
126 | }
127 |
128 | pkt_len |= *buf->tail;
129 | rem--;
130 |
131 | if (rem > 0) {
132 | buf->tail++;
133 | } else {
134 | buf->tail = buf->base;
135 | rem = buf->capacity;
136 | }
137 |
138 | if (pkt_len > *len) {
139 | buf->tail = old_tail;
140 | return -ETOOLONG;
141 | }
142 |
143 | if (rem <= pkt_len) {
144 | memcpy(data, buf->tail, rem);
145 | buf->tail = buf->base;
146 | memcpy(data + rem, buf->tail, pkt_len - rem);
147 | buf->tail += pkt_len - rem;
148 | } else {
149 | memcpy(data, buf->tail, pkt_len);
150 | buf->tail += pkt_len;
151 | }
152 |
153 | buf->depth -= 1;
154 | *len = pkt_len;
155 |
156 | return ESUCCESS;
157 | }
158 |
159 | void pkt_buf_clear(struct pkt_buf *buf)
160 | {
161 | buf->head = buf->base;
162 | buf->tail = buf->base;
163 | buf->depth = 0;
164 | }
165 |
166 | int pkt_buf_depth(struct pkt_buf *buf)
167 | {
168 | return buf->depth;
169 | }
170 |
171 | int pkt_buf_size(struct pkt_buf *buf)
172 | {
173 | if (buf->head >= buf->tail) {
174 | return buf->head - buf->tail;
175 | } else {
176 | return buf->capacity - (buf->tail - buf->head);
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/msp430_uart.c:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #include
20 | #include "rbuf.h"
21 |
22 | //case-dependent aliases for pins and peripheral things
23 | #define UCAxCTLW0 UCA2CTLW0
24 | #define UCAxBRW UCA2BRW
25 | #define UCAxMCTLW UCA2MCTLW
26 | #define UCAxIE UCA2IE
27 | #define UCAxIV UCA2IV
28 | #define UCAxIFG UCA2IFG
29 | #define UCAxTXBUF UCA2TXBUF
30 | #define UCAxRXBUF UCA2RXBUF
31 | #define EUSCI_Ax_VECTOR EUSCI_A2_VECTOR
32 | #define USCI_Ax_ISR USCI_A2_ISR
33 |
34 | rbuf txbuf, rxbuf;
35 | uint8_t uartInitialized=0;
36 |
37 |
38 | void uart_init_pins(){
39 |
40 | // Note: the correct setting here varies by port
41 | // USCI_A2 UART operation
42 | P5SEL0 |= (BIT4 | BIT5);
43 | P5SEL1 &= ~(BIT4 | BIT5);
44 | }
45 |
46 |
47 | int uart_init(){
48 | uart_init_pins();
49 | rbuf_init(&rxbuf);
50 | rbuf_init(&txbuf);
51 | // Configure USCI_Ax for UART mode
52 | UCAxCTLW0 = UCSWRST; // Put eUSCI in reset
53 | UCAxCTLW0 |= UCSSEL__SMCLK; // CLK = SMCLK
54 |
55 | // Assuming 8 MHz SMCLK
56 | // See User's Guide Table 30-5 on page 779
57 |
58 | // 9600 baud
59 | // UCAxBRW = 52;
60 | // // UCBRFx UCBRSx
61 | // UCAxMCTLW |= UCOS16 | 0x0001 | 0x4900;
62 |
63 | // 38400
64 | // UCAxBRW = 13;
65 | // // UCBRFx UCBRSx
66 | // UCAxMCTLW = UCOS16 | UCBRF_0 | 0x8400;
67 |
68 | // 115200
69 | UCAxBRW = 4;
70 | // UCBRFx UCBRSx
71 | UCAxMCTLW = UCOS16 | UCBRF_5 | 0x5500;
72 |
73 |
74 | UCAxCTLW0 &= ~UCSWRST; // Initialize eUSCI
75 |
76 | UCAxIE |= UCRXIE; // Enable USCI_Ax RX interrupt
77 | __bis_SR_register(GIE); // enable global interrupts
78 | return 0;
79 | }
80 |
81 | int uart_putc(char c){
82 | int err;
83 | unsigned short sreg = __get_interrupt_state();
84 | do{
85 | __disable_interrupt(); //disable interrupts while accessing UART buffer
86 | err=rbuf_put(&txbuf, c); //attempt to put c in the transmit queue
87 | __set_interrupt_state(sreg);//return interrupts to previous enable/disable state
88 | }while(err<0); //and keep trying until it fits
89 | UCAxIE |= UCTXIE; //enable the interrupt if it wasn't already
90 | return 0;
91 | }
92 |
93 | int uart_putbuf(char* buf, int len){
94 | int i;
95 | for(i=0;i.
17 | */
18 |
19 | #include
20 | #include
21 | #include
22 | #include "error.h"
23 | #include "lib446x/si446x.h"
24 | #include "lfr.h"
25 | #include "pins.h"
26 | #include "mcu.h"
27 | #include "modem_configs.h"
28 | #include "radio.h"
29 |
30 | int set_frequency(uint32_t freq)
31 | {
32 | uint8_t outdiv;
33 | uint8_t band;
34 | int err;
35 |
36 | struct si446x_part_info info;
37 |
38 | err = si446x_get_part_info(&dev, &info);
39 |
40 | if (err) {
41 | return err;
42 | }
43 |
44 | if (info.part == 0x4464) {
45 | if (freq > 119000000 && freq < 159000000) {
46 | outdiv = 24;
47 | band = 5;
48 | } else if (freq > 177000000 && freq < 239000000) {
49 | outdiv = 16;
50 | band = 4;
51 | } else if (freq > 235000000 && freq < 319000000) {
52 | outdiv = 12;
53 | band = 3;
54 | } else if (freq > 353000000 && freq < 479000000) {
55 | outdiv = 8;
56 | band = 2;
57 | } else if (freq > 470000000 && freq < 639000000) {
58 | outdiv = 6;
59 | band = 1;
60 | } else if (freq > 705000000 && freq < 960000000) {
61 | outdiv = 4;
62 | band = 0;
63 | } else {
64 | return -EINVAL;
65 | }
66 | } else { // 4460, 4461, 4463
67 | if (freq > 142000000 && freq < 175000000) {
68 | outdiv = 24;
69 | band = 5;
70 | } else if (freq > 284000000 && freq < 350000000) {
71 | outdiv = 12;
72 | band = 3;
73 | } else if (freq > 420000000 && freq < 525000000) {
74 | outdiv = 8;
75 | band = 2;
76 | } else if (freq > 850000000 && freq < 1050000000) {
77 | outdiv = 4;
78 | band = 0;
79 | } else {
80 | return -EINVAL;
81 | }
82 | }
83 |
84 | // fc = F / (2 * xo_freq / outdiv) * 2^19
85 | uint32_t fc = (uint32_t) (outdiv * ((uint64_t) freq << 18) / XTAL_FREQ);
86 |
87 | return si446x_set_frequency(&dev, fc, band);
88 | }
89 |
90 | int set_modem_config(uint8_t cfg)
91 | {
92 |
93 | static const struct modem_cfg cfg_5k_data_1k25_dev_struct = CFG_DATA_5K_DEV_1K25;
94 | static const struct modem_cfg cfg_5k_data_2k5_dev_struct = CFG_DATA_5K_DEV_2K5;
95 | static const struct modem_cfg cfg_10k_data_2k5_dev_struct = CFG_DATA_10K_DEV_2K5;
96 | static const struct modem_cfg cfg_10k_data_5k_dev_struct = CFG_DATA_10K_DEV_5K;
97 | static const struct modem_cfg cfg_25k_data_6k25_dev_struct = CFG_DATA_25K_DEV_6K25;
98 | static const struct modem_cfg cfg_25k_data_12k5_dev_struct = CFG_DATA_25K_DEV_12K5;
99 | static const struct modem_cfg cfg_50k_data_12k5_dev_struct = CFG_DATA_50K_DEV_12K5;
100 | static const struct modem_cfg cfg_50k_data_25k_dev_struct = CFG_DATA_50K_DEV_25K;
101 | static const struct modem_cfg cfg_100k_data_25k_dev_struct = CFG_DATA_100K_DEV_25K;
102 | static const struct modem_cfg cfg_100k_data_50k_dev_struct = CFG_DATA_100K_DEV_50K;
103 |
104 | const struct modem_cfg *cfg_struct;
105 |
106 | switch (cfg) {
107 | case DATA_5K_DEV_1K25:
108 | cfg_struct = &cfg_5k_data_1k25_dev_struct;
109 | break;
110 |
111 | case DATA_5K_DEV_2K5:
112 | cfg_struct = &cfg_5k_data_2k5_dev_struct;
113 | break;
114 |
115 | case DATA_10K_DEV_2K5:
116 | cfg_struct = &cfg_10k_data_2k5_dev_struct;
117 | break;
118 |
119 | case DATA_10K_DEV_5K:
120 | cfg_struct = &cfg_10k_data_5k_dev_struct;
121 | break;
122 |
123 | case DATA_25K_DEV_6K25:
124 | cfg_struct = &cfg_25k_data_6k25_dev_struct;
125 | break;
126 |
127 | case DATA_25K_DEV_12K5:
128 | cfg_struct = &cfg_25k_data_12k5_dev_struct;
129 | break;
130 |
131 | case DATA_50K_DEV_12K5:
132 | cfg_struct = &cfg_50k_data_12k5_dev_struct;
133 | break;
134 |
135 | case DATA_50K_DEV_25K:
136 | cfg_struct = &cfg_50k_data_25k_dev_struct;
137 | break;
138 |
139 | case DATA_100K_DEV_25K:
140 | cfg_struct = &cfg_100k_data_25k_dev_struct;
141 | break;
142 |
143 | case DATA_100K_DEV_50K:
144 | cfg_struct = &cfg_100k_data_50k_dev_struct;
145 | break;
146 |
147 |
148 | default:
149 | if (cfg & 0xF0) {
150 | return -EINVAL;
151 | } else {
152 | return -ENOTIMPL;
153 | }
154 | }
155 |
156 | int err;
157 |
158 | err = si446x_send_cfg_data_wait(&dev,
159 | sizeof(cfg_struct->modem_mod_type_12),
160 | cfg_struct->modem_mod_type_12);
161 | if (err) {
162 | return err;
163 | }
164 |
165 | err = si446x_send_cfg_data_wait(&dev,
166 | sizeof(cfg_struct->modem_freq_dev_0_1),
167 | cfg_struct->modem_freq_dev_0_1);
168 | if (err) {
169 | return err;
170 | }
171 |
172 | err = si446x_send_cfg_data_wait(&dev,
173 | sizeof(cfg_struct->modem_tx_ramp_delay_8),
174 | cfg_struct->modem_tx_ramp_delay_8);
175 | if (err) {
176 | return err;
177 | }
178 |
179 | err = si446x_send_cfg_data_wait(&dev,
180 | sizeof(cfg_struct->modem_bcr_osr_1_9),
181 | cfg_struct->modem_bcr_osr_1_9);
182 | if (err) {
183 | return err;
184 | }
185 |
186 | err = si446x_send_cfg_data_wait(&dev,
187 | sizeof(cfg_struct->modem_afc_gear),
188 | cfg_struct->modem_afc_gear);
189 | if (err) {
190 | return err;
191 | }
192 |
193 | err = si446x_send_cfg_data_wait(&dev,
194 | sizeof(cfg_struct->modem_agc_window_size_9),
195 | cfg_struct->modem_agc_window_size_9);
196 | if (err) {
197 | return err;
198 | }
199 |
200 | err = si446x_send_cfg_data_wait(&dev,
201 | sizeof(cfg_struct->modem_ook_cnt1_9),
202 | cfg_struct->modem_ook_cnt1_9);
203 | if (err) {
204 | return err;
205 | }
206 |
207 | err = si446x_send_cfg_data_wait(&dev,
208 | sizeof(cfg_struct->modem_chflt_coe13_7_0_12),
209 | cfg_struct->modem_chflt_coe13_7_0_12);
210 | if (err) {
211 | return err;
212 | }
213 |
214 | err = si446x_send_cfg_data_wait(&dev,
215 | sizeof(cfg_struct->modem_chflt_coe1_7_0_12),
216 | cfg_struct->modem_chflt_coe1_7_0_12);
217 | if (err) {
218 | return err;
219 | }
220 |
221 | err = si446x_send_cfg_data_wait(&dev,
222 | sizeof(cfg_struct->modem_chflt_coe7_7_0_12),
223 | cfg_struct->modem_chflt_coe7_7_0_12);
224 | if (err) {
225 | return err;
226 | }
227 |
228 | err = si446x_send_cfg_data_wait(&dev,
229 | sizeof(cfg_struct->rf_synth_pfdcp_cpff),
230 | cfg_struct->rf_synth_pfdcp_cpff);
231 |
232 | return err;
233 |
234 | }
235 |
236 |
237 |
--------------------------------------------------------------------------------
/util/ber_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2
2 | # -*- coding: utf-8 -*-
3 |
4 | ##################################################
5 | # GNU Radio Python Flow Graph
6 | ##################################################
7 |
8 | from gnuradio import analog
9 | from gnuradio import blocks
10 | from gnuradio import eng_notation
11 | from gnuradio import filter
12 | from gnuradio import gr
13 | from gnuradio import uhd
14 | from gnuradio.eng_option import eng_option
15 | from gnuradio.filter import firdes
16 | from optparse import OptionParser
17 | import limesdr
18 | import math
19 | import numpy
20 | import time
21 | import sys
22 | import pmt
23 | import si446x
24 | import serial
25 | import struct
26 | import logging
27 | from threading import Timer
28 | import thread
29 | import os
30 | from enum import Enum
31 | from com_radio_2 import Radio
32 |
33 | class auto_ber_test(gr.top_block):
34 |
35 | def __init__(self, samp_rate, tune_offset, source, sink, sw_gain=0, sym_rate=10e3, deviation=20e3):
36 | gr.top_block.__init__(self, "Auto BER Test")
37 |
38 | self.source = source
39 | self.sink = sink
40 |
41 | ##################################################
42 | # Variables
43 | ##################################################
44 | self.sym_rate = sym_rate
45 | self.samp_per_sym = samp_per_sym = int(samp_rate / sym_rate)
46 | self.bt = bt = 0.5
47 | self.gaussian_taps = gaussian_taps = firdes.gaussian(1.0,samp_per_sym, bt, 4 * samp_per_sym)
48 | self.tx_taps = tx_taps = numpy.convolve(numpy.array(gaussian_taps),numpy.array((1,) * samp_per_sym))
49 | self.tx_deviation = tx_deviation = deviation
50 | self.sw_gain = sw_gain
51 |
52 | ##################################################
53 | # Blocks
54 | ##################################################
55 |
56 | # Disable CRC generation to make sure we're in the right CRC_ENABLE mode
57 | self.var_len_packet_creator = si446x.var_len_packet_creator(8, 0x2DD4, True, 0x0000)
58 | self.pdu_to_tagged_stream = blocks.pdu_to_tagged_stream(blocks.byte_t, 'packet_len')
59 |
60 | self.uchar_to_float = blocks.uchar_to_float()
61 | self.sub_one_half = blocks.add_const_vff((-0.5, ))
62 | self.mult_two = blocks.multiply_const_vff((2, ))
63 |
64 | self.gaussian_filter = filter.interp_fir_filter_fff(samp_per_sym, (tx_taps))
65 | self.gaussian_filter.declare_sample_delay(0)
66 | self.freq_xlating_fir_filter = filter.freq_xlating_fir_filter_ccc(1, ([10**(sw_gain / 20.0)]), tune_offset, samp_rate)
67 | self.freq_mod = analog.frequency_modulator_fc(2 * math.pi * tx_deviation / samp_rate)
68 |
69 | ##################################################
70 | # Connections
71 | ##################################################
72 | self.msg_connect((self.source, 'out'), (self.var_len_packet_creator, 'in'))
73 | self.msg_connect((self.var_len_packet_creator, 'out'), (self.pdu_to_tagged_stream, 'pdus'))
74 | self.connect((self.pdu_to_tagged_stream, 0), (self.uchar_to_float, 0))
75 | self.connect((self.freq_mod, 0), (self.freq_xlating_fir_filter, 0))
76 | self.connect((self.freq_xlating_fir_filter, 0), (self.sink, 0))
77 | self.connect((self.gaussian_filter, 0), (self.freq_mod, 0))
78 | self.connect((self.mult_two, 0), (self.gaussian_filter, 0))
79 | self.connect((self.sub_one_half, 0), (self.mult_two, 0))
80 | self.connect((self.uchar_to_float, 0), (self.sub_one_half, 0))
81 |
82 | def get_sw_gain(self):
83 | return self.sw_gain
84 |
85 | def set_sw_gain(self, sw_gain):
86 | self.sw_gain = sw_gain
87 | self.freq_xlating_fir_filter.set_taps(([10**(self.sw_gain / 20.0)]))
88 |
89 | class msg_source(gr.basic_block):
90 |
91 | def __init__(self):
92 |
93 | gr.basic_block.__init__(
94 | self,
95 | name="msg_source",
96 | in_sig=None,
97 | out_sig=None)
98 |
99 | self.message_port_register_out(pmt.intern('out'))
100 |
101 | def transmit(self, data):
102 | vector = pmt.make_u8vector(len(data), 0)
103 | for i, c in enumerate(data):
104 | pmt.u8vector_set(vector, i, ord(data[i]))
105 | pdu = pmt.cons(pmt.make_dict(), vector)
106 | self.message_port_pub(pmt.intern('out'), pdu)
107 |
108 |
109 | ########################################
110 | # Main #
111 | ########################################
112 |
113 | def main():
114 | ########################################
115 | # Configuration #
116 | ########################################
117 |
118 | port = '/dev/tty.SLAB_USBtoUART'
119 | baud = 115200
120 |
121 | freq = 434.000e6
122 | tune_offset = 250e3
123 | samp_rate = 2e6
124 | sym_rate = 10e3
125 | deviation = 5e3
126 | lfr_modem_cfg = 7
127 |
128 | usrp = True
129 | lime = False
130 | lime_serial = '1D3AC9891D2233'
131 |
132 | num_pkts = 100
133 |
134 | hw_gain = 30
135 | hw_atten = 60
136 | usrp_chan_power = -36.6
137 |
138 | gain_offset = usrp_chan_power - hw_atten
139 |
140 | lvls = (numpy.linspace(-110.0, -100.0, 11) - gain_offset).tolist()
141 |
142 | ########################################
143 |
144 | sys.stdout = open('/dev/null', 'w')
145 | logging.basicConfig(format='%(message)s', stream=sys.stderr, level=logging.DEBUG)
146 |
147 | if usrp:
148 | sink = uhd.usrp_sink(
149 | ",".join(("", "")),
150 | uhd.stream_args(
151 | cpu_format="fc32",
152 | channels=range(1),
153 | ),
154 | )
155 | sink.set_samp_rate(samp_rate)
156 | sink.set_center_freq(freq + tune_offset, 0)
157 | sink.set_gain(hw_gain, 0)
158 | sink.set_antenna('TX/RX', 0)
159 | elif lime:
160 | sink = limesdr.sink(lime_serial, 1, 1, 0, 0, '', freq + tune_offset, samp_rate, 0, 0, 10e6, 0, 10e6, 1, 1, 1, 1,
161 | 5e6, 1, 5e6, 0, 0, 0, 0, hw_gain, 30, 0, 0, 0, 0)
162 | else:
163 | logging.error("ERROR: Select a radio type!")
164 | sys.exit(1)
165 |
166 | source = msg_source()
167 |
168 | tb = auto_ber_test(samp_rate, tune_offset, source, sink, 0, sym_rate=sym_rate, deviation=deviation)
169 | tb.start()
170 |
171 | pkt_data = numpy.random.bytes(255)
172 |
173 | lfr = Radio(port, baud)
174 |
175 | cfg = lfr.get_cfg()
176 | cfg['flags'] &= ~(1) # CRC_ENABLE
177 | cfg['modem_config'] = (cfg['modem_config'] & 0xF0) | lfr_modem_cfg
178 | lfr.set_cfg(cfg)
179 |
180 |
181 | # Grr... Python scoping...
182 | global timer
183 | timer = None
184 |
185 | global num_bits
186 | global num_errs
187 | num_bits = 0
188 | num_errs = 0
189 |
190 | pkt_delay = 2500 / sym_rate
191 |
192 | def step_pwr_lvl():
193 | global num_bits
194 | global num_errs
195 | logging.info('')
196 | logging.info('Power: {: >3.1f} dBm Bits received: {: >8} Bits Errors: {: >5} log10(BER)= {:.02f}'\
197 | .format(lvls[0] + gain_offset, num_bits, num_errs, math.log10(num_errs + 0.001) - math.log10(num_bits + 0.001)))
198 |
199 | lvls[:] = lvls[1:]
200 | if not lvls:
201 | tb.stop()
202 | thread.interrupt_main()
203 | # Ugly, skips any finally block
204 | # Essentially crashes
205 | os._exit(0)
206 |
207 | logging.info('Stepping to power level: {} dBm'.format(lvls[0] + gain_offset))
208 | tb.set_sw_gain(lvls[0])
209 |
210 | num_bits = 0
211 | num_errs = 0
212 |
213 | for _ in range(num_pkts):
214 | source.transmit(pkt_data)
215 |
216 | global timer
217 | timer = Timer(num_pkts * pkt_delay, step_pwr_lvl)
218 | timer.start()
219 |
220 | timer = Timer(num_pkts * pkt_delay, step_pwr_lvl)
221 |
222 | logging.info('LFR BER Test')
223 | logging.info('Sym Rate: {:.03f} kbit/s Deviation: {:.03f} kHz LFR Modem Config: 0x{:02X}'.format(sym_rate / 1000.0, deviation / 1000.0, lfr_modem_cfg))
224 | logging.info('-' * 80)
225 |
226 | logging.info('Starting at power level: {} dBm'.format(lvls[0] + gain_offset))
227 | tb.set_sw_gain(lvls[0])
228 |
229 | for _ in range(num_pkts):
230 | source.transmit(pkt_data)
231 |
232 | timer.start()
233 |
234 | while True:
235 | data = [ord(x) for x in lfr.rx()]
236 |
237 | for x, y in zip(data, pkt_data):
238 | xor = x ^ ord(y)
239 | for _ in range(8):
240 | num_bits += 1
241 | if (xor & 1):
242 | num_errs += 1
243 | xor >>= 1
244 |
245 |
246 | if __name__ == '__main__':
247 | main()
248 |
--------------------------------------------------------------------------------
/util/com_radio_2.py:
--------------------------------------------------------------------------------
1 | import serial
2 | import struct
3 | from enum import Enum
4 |
5 | ########################################
6 | # LFR Command Handling #
7 | ########################################
8 |
9 | SYNCWORD_H = 0xBE
10 | SYNCWORD_L = 0xEF
11 |
12 | class ParseState(Enum):
13 | SYNC_H = 0
14 | SYNC_L = 1
15 | CMD = 3
16 | PAYLOAD_LEN = 4
17 | PAYLOAD = 5
18 | CHKSUM_H = 6
19 | CHKSUM_L = 7
20 |
21 | class Command(Enum):
22 | NOP = 0x00
23 | RESET = 0x01
24 | TXDATA = 0x02
25 | GET_TXPWR = 0x03
26 | SET_TXPWR = 0x04
27 |
28 | GET_CFG = 0x08
29 | SET_CFG = 0x09
30 | SAVE_CFG = 0x0A
31 | CFG_DEFAULT = 0x0B
32 |
33 | RXDATA = 0x10
34 |
35 | ERROR = 0x7F
36 |
37 | REPLY = 0x80
38 |
39 | def feltcher(chksum, byte):
40 | lsb = chksum & 0xFF
41 | msb = chksum >> 8
42 | msb += byte
43 | msb &= 0xFF
44 | lsb += msb
45 | lsb &= 0xFF
46 | return (msb << 8) | lsb
47 |
48 | def compute_chksum(data):
49 | chksum = 0
50 | for x in data:
51 | chksum = feltcher(chksum, ord(x))
52 | return chksum
53 |
54 | class RadioException(Exception):
55 |
56 | def __init__(self, code):
57 | self.code = code
58 |
59 | if code == 0:
60 | self.error = 'ESUCCESS'
61 | self.msg = 'command succeeded'
62 | elif code == 1:
63 | self.error = 'ETIMEOUT'
64 | self.msg = 'timeout waiting for CTS'
65 | elif code == 2:
66 | self.error = 'EWRONGPART'
67 | self.msg = 'unsupported part number'
68 | elif code == 3:
69 | self.error = 'EINVAL'
70 | self.msg = 'invalid parameter'
71 | elif code == 4:
72 | self.error = 'EINVALSTATE'
73 | self.msg = 'invalid internal state'
74 | elif code == 5:
75 | self.error = 'ETOOLONG'
76 | self.msg = 'packet too long'
77 | elif code == 6:
78 | self.error = 'ECHKSUM'
79 | self.msg = 'invalid checksum'
80 | elif code == 7:
81 | self.error = 'EBUSY'
82 | self.msg = 'pending operation'
83 | elif code == 8:
84 | self.error = 'ERXTIMEOUT'
85 | self.msg = 'Si446x RX timed out (zero len bug?)'
86 | else:
87 | self.error = 'UNKNOWN'
88 | self.msg = 'An unknown error occurred'
89 |
90 | def __str__(self):
91 | return 'RadioException(' + self.error + '): ' + self.msg
92 |
93 | class Radio:
94 |
95 | def __init__(self, port, baud=115200):
96 | self.ser = serial.Serial(port, baud)
97 | self.state = ParseState.SYNC_H
98 |
99 | def tx(self, data):
100 | pkt = chr(Command.TXDATA.value)
101 | pkt += chr(len(data))
102 | pkt += data
103 | chksum = compute_chksum(pkt)
104 | pkt += chr(chksum >> 8)
105 | pkt += chr(chksum & 0xFF)
106 | pkt = '\xBE\xEF' + pkt
107 |
108 | self.ser.write(pkt)
109 |
110 | (cmd, pay) = self.recv()
111 |
112 | if cmd == Command.ERROR.value | Command.REPLY.value:
113 | err = RadioException(ord(pay[0]))
114 | if err.error == 'EBUSY':
115 | self.tx(data)
116 | else:
117 | raise err
118 |
119 | elif Command.TXDATA.value | Command.REPLY.value:
120 | return
121 | else:
122 | raise Exception('Unexpected response: ' + str((cmd, pay)))
123 |
124 | def rx(self):
125 | (cmd, pay) = self.recv()
126 |
127 | if cmd == Command.ERROR.value | Command.REPLY.value:
128 | raise RadioException(pay[0])
129 | elif Command.RXDATA.value | Command.REPLY.value:
130 | return pay
131 | else:
132 | raise Exception('Unexpected response: ' + str((cmd, pay)))
133 |
134 | def recv(self):
135 |
136 | payload = ''
137 |
138 | while True:
139 | c = ord(self.ser.read(1))
140 |
141 | if self.state == ParseState.SYNC_H:
142 | if c == SYNCWORD_H:
143 | self.state = ParseState.SYNC_L
144 | elif self.state == ParseState.SYNC_L:
145 | if c == SYNCWORD_L:
146 | self.state = ParseState.CMD
147 | elif c == SYNCWORD_H:
148 | self.state = ParseState.SYNC_L
149 | else:
150 | self.state = ParseState.SYNC_H
151 | elif self.state == ParseState.CMD:
152 | cmd = c
153 | self.state = ParseState.PAYLOAD_LEN
154 | elif self.state == ParseState.PAYLOAD_LEN:
155 | length = c
156 | # TODO: Validate len for cmd
157 | if (length):
158 | self.state = ParseState.PAYLOAD
159 | else:
160 | chksum = compute_chksum(''.join([chr(cmd), chr(0)]))
161 | self.state = ParseState.CHKSUM_H
162 |
163 | elif self.state == ParseState.PAYLOAD:
164 | payload += chr(c)
165 | length -= 1
166 | self.state = ParseState.PAYLOAD
167 | if (length == 0):
168 | chksum = compute_chksum(''.join([chr(cmd), chr(len(payload))]) + payload)
169 | self.state = ParseState.CHKSUM_H
170 | elif self.state == ParseState.CHKSUM_H:
171 | if (c == chksum >> 8):
172 | self.state = ParseState.CHKSUM_L
173 | else:
174 | # TODO: Handle error
175 | pass
176 | self.state = ParseState.SYNC_H
177 | break
178 | elif self.state == ParseState.CHKSUM_L:
179 | if (c != chksum & 0xFF):
180 | # TODO: Handle error
181 | pass
182 | self.state = ParseState.SYNC_H
183 | break
184 |
185 | return (0, cmd, payload)
186 |
187 | def get_cfg(self):
188 | pkt = chr(Command.GET_CFG.value)
189 | pkt += chr(0)
190 | chksum = compute_chksum(pkt)
191 | pkt += chr(chksum >> 8)
192 | pkt += chr(chksum & 0xFF)
193 | pkt = '\xBE\xEF' + pkt
194 |
195 | self.ser.write(pkt)
196 |
197 | (cmd, pay) = self.recv()
198 |
199 | if cmd == Command.ERROR.value | Command.REPLY.value:
200 | err = RadioException(pay[0])
201 | raise err
202 |
203 | elif Command.GET_CFG.value | Command.REPLY.value:
204 |
205 | cfg = {}
206 |
207 | cfg['cfg_ver'], cfg['freq'], cfg['modem_config'], \
208 | cfg['txco_vpull'], cfg['tx_gate_bias'], cfg['tx_vdd'], \
209 | cfg['pa_ilimit'], cfg['tx_vdd_delay'], cfg['flags'],\
210 | cfg['callsign'] = struct.unpack("!BIBHHHHHH8s", pay)
211 |
212 | return cfg
213 | else:
214 | raise Exception('Unexpected response: ' + str((cmd, pay)))
215 |
216 | def set_cfg(self, cfg_in):
217 |
218 | cfg = dict(cfg_in)
219 |
220 | data = struct.pack("!BIBHHHHHH8s", \
221 | cfg['cfg_ver'], cfg['freq'], cfg['modem_config'], \
222 | cfg['txco_vpull'], cfg['tx_gate_bias'], cfg['tx_vdd'], \
223 | cfg['pa_ilimit'], cfg['tx_vdd_delay'], cfg['flags'], \
224 | cfg['callsign'])
225 |
226 | pkt = chr(Command.SET_CFG.value)
227 | pkt += chr(len(data))
228 | pkt += data
229 | chksum = compute_chksum(pkt)
230 | pkt += chr(chksum >> 8)
231 | pkt += chr(chksum & 0xFF)
232 | pkt = '\xBE\xEF' + pkt
233 |
234 | self.ser.write(pkt)
235 |
236 | (cmd, pay) = self.recv()
237 |
238 | if cmd == Command.ERROR.value | Command.REPLY.value:
239 | err = RadioException(ord(pay[0]))
240 | raise err
241 |
242 | elif Command.SET_CFG.value | Command.REPLY.value:
243 | return
244 | else:
245 | raise Exception('Unexpected response: ' + str((cmd, pay)))
246 |
247 |
248 | def save_cfg(self):
249 | pkt = chr(Command.SAVE_CFG.value)
250 | pkt += chr(0)
251 | chksum = compute_chksum(pkt)
252 | pkt += chr(chksum >> 8)
253 | pkt += chr(chksum & 0xFF)
254 | pkt = '\xBE\xEF' + pkt
255 |
256 | self.ser.write(pkt)
257 |
258 | (cmd, pay) = self.recv()
259 |
260 | if cmd == Command.ERROR.value | Command.REPLY.value:
261 | err = RadioException(ord(pay[0]))
262 | raise err
263 |
264 | elif Command.GET_CFG.value | Command.REPLY.value:
265 | return
266 | else:
267 | raise Exception('Unexpected response: ' + str((cmd, pay)))
268 |
269 | def cfg_default(self):
270 | pkt = chr(Command.CFG_DEFAULT.value)
271 | pkt += chr(0)
272 | chksum = compute_chksum(pkt)
273 | pkt += chr(chksum >> 8)
274 | pkt += chr(chksum & 0xFF)
275 | pkt = '\xBE\xEF' + pkt
276 |
277 | self.ser.write(pkt)
278 |
279 | (cmd, pay) = self.recv()
280 |
281 | if cmd == Command.ERROR.value | Command.REPLY.value:
282 | err = RadioException(ord(pay[0]))
283 | raise err
284 |
285 | elif Command.GET_CFG.value | Command.REPLY.value:
286 | return
287 | else:
288 | raise Exception('Unexpected response: ' + str((cmd, pay)))
289 |
--------------------------------------------------------------------------------
/util/selectivity_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2
2 | # -*- coding: utf-8 -*-
3 |
4 | ##################################################
5 | # GNU Radio Python Flow Graph
6 | ##################################################
7 |
8 | from gnuradio import analog
9 | from gnuradio import blocks
10 | from gnuradio import eng_notation
11 | from gnuradio import filter
12 | from gnuradio import gr
13 | from gnuradio import uhd
14 | from gnuradio import digital
15 | from gnuradio.eng_option import eng_option
16 | from gnuradio.filter import firdes
17 | import si446x
18 | from optparse import OptionParser
19 | import math
20 | import numpy
21 | import time
22 | import sys
23 | import pmt
24 | import logging
25 | import kiss
26 | from threading import Timer
27 | import thread
28 | import os
29 | import json
30 |
31 |
32 | from com_radio_2 import Radio
33 |
34 | class auto_ber_test(gr.top_block):
35 |
36 | def __init__(self, samp_rate, source, sink, sw_gain=0, interfering_gain=-100, interfering_offset=50e3):
37 | gr.top_block.__init__(self, "Auto BER Test")
38 |
39 | self.source = source
40 | self.sink = sink
41 |
42 | ##################################################
43 | # Variables
44 | ##################################################
45 | self.sym_rate = sym_rate = 10000
46 | self.samp_per_sym = samp_per_sym = int(samp_rate / sym_rate)
47 | self.bt = bt = 0.5
48 | self.gaussian_taps = gaussian_taps = firdes.gaussian(1.0,samp_per_sym, bt, 4 * samp_per_sym)
49 | self.tx_taps = tx_taps = numpy.convolve(numpy.array(gaussian_taps),numpy.array((1,) * samp_per_sym))
50 | self.tx_deviation = tx_deviation = 20e3
51 | self.sw_gain = sw_gain
52 |
53 |
54 | self.interfering_offset = interfering_offset
55 | self.interfering_gain = interfering_gain
56 |
57 | ##################################################
58 | # Blocks
59 | ##################################################
60 |
61 | # Disable CRC generation to make sure we're in the right CRC_ENABLE mode
62 | self.var_len_packet_creator = si446x.var_len_packet_creator(8, 0x2DD4, True, 0x0000)
63 | self.pdu_to_tagged_stream = blocks.pdu_to_tagged_stream(blocks.byte_t, 'packet_len')
64 |
65 | self.uchar_to_float = blocks.uchar_to_float()
66 | self.sub_one_half = blocks.add_const_vff((-0.5, ))
67 | self.mult_two = blocks.multiply_const_vff((2, ))
68 |
69 | self.gaussian_filter = filter.interp_fir_filter_fff(samp_per_sym, (tx_taps))
70 | self.gaussian_filter.declare_sample_delay(0)
71 | self.freq_mod = analog.frequency_modulator_fc(2 * math.pi * tx_deviation / samp_rate)
72 |
73 | self.interfering_src = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, interfering_offset, 10**(interfering_gain / 20.0), 0)
74 | self.multiply_sw_gain = blocks.multiply_const_vcc((10**(sw_gain / 20.0), ))
75 | self.blocks_add = blocks.add_vcc(1)
76 |
77 |
78 | ##################################################
79 | # Connections
80 | ##################################################
81 | self.msg_connect((self.var_len_packet_creator, 'out'), (self.pdu_to_tagged_stream, 'pdus'))
82 | self.msg_connect((self.source, 'out'), (self.var_len_packet_creator, 'in'))
83 | self.connect((self.blocks_add, 0), (self.sink, 0))
84 | self.connect((self.freq_mod, 0), (self.multiply_sw_gain, 0))
85 | self.connect((self.gaussian_filter, 0), (self.freq_mod, 0))
86 | self.connect((self.interfering_src, 0), (self.blocks_add, 1))
87 | self.connect((self.mult_two, 0), (self.gaussian_filter, 0))
88 | self.connect((self.multiply_sw_gain, 0), (self.blocks_add, 0))
89 | self.connect((self.pdu_to_tagged_stream, 0), (self.uchar_to_float, 0))
90 | self.connect((self.sub_one_half, 0), (self.mult_two, 0))
91 | self.connect((self.uchar_to_float, 0), (self.sub_one_half, 0))
92 |
93 | def get_sw_gain(self):
94 | return self.sw_gain
95 |
96 | def set_sw_gain(self, sw_gain):
97 | self.sw_gain = sw_gain
98 | self.multiply_sw_gain.set_k((10**(self.sw_gain / 20.0), ))
99 |
100 | def get_interfering_offset(self):
101 | return self.interfering_offset
102 |
103 | def set_interfering_offset(self, interfering_offset):
104 | self.interfering_offset = interfering_offset
105 | self.interfering_src.set_frequency(self.interfering_offset)
106 |
107 | def get_interfering_gain(self):
108 | return self.interfering_gain
109 |
110 | def set_interfering_gain(self, interfering_gain):
111 | self.interfering_gain = interfering_gain
112 | self.interfering_src.set_amplitude(10**(self.interfering_gain / 20.0))
113 |
114 |
115 | class msg_source(gr.basic_block):
116 |
117 | def __init__(self):
118 |
119 | gr.basic_block.__init__(
120 | self,
121 | name="msg_source",
122 | in_sig=None,
123 | out_sig=None)
124 |
125 | self.message_port_register_out(pmt.intern('out'))
126 |
127 | def transmit(self, data):
128 | vector = pmt.make_u8vector(len(data), 0)
129 | for i, c in enumerate(data):
130 | pmt.u8vector_set(vector, i, ord(data[i]))
131 | pdu = pmt.cons(pmt.make_dict(), vector)
132 | self.message_port_pub(pmt.intern('out'), pdu)
133 |
134 | ########################################
135 | # Main #
136 | ########################################
137 |
138 | def main():
139 | ########################################
140 | # Configuration #
141 | ########################################
142 |
143 | port = "/dev/tty.SLAB_USBtoUART"
144 | baud = 115200
145 |
146 | freq = 434.000e6
147 | lfr_freq = 434.000e6
148 | tune_offset = 10e6
149 | samp_rate = 1e6 * 2
150 |
151 | usrp = True
152 |
153 | num_pkts = 20
154 | ms_per_packet = 250
155 |
156 | # hw_gain = 20
157 | # gain_offset = -46 - 60
158 |
159 | hw_gain = 68
160 | gain_offset = -67 - 60 + hw_gain
161 |
162 | interfering_offset = 75e3
163 | # Can't be larger than sw_gain
164 | interfering_rel_gains = numpy.linspace(0, 50, 6).tolist()
165 |
166 | sw_gain = -50
167 |
168 | ########################################
169 |
170 | sys.stdout = open('/dev/null', 'w')
171 | logging.basicConfig(format='%(message)s', stream=sys.stderr, level=logging.DEBUG)
172 |
173 | if usrp:
174 | sink = uhd.usrp_sink(
175 | ",".join(("", "")),
176 | uhd.stream_args(
177 | cpu_format="fc32",
178 | channels=range(1),
179 | ),
180 | )
181 | sink.set_samp_rate(samp_rate)
182 | sink.set_center_freq(uhd.tune_request(freq, tune_offset), 0)
183 | sink.set_gain(hw_gain, 0)
184 | sink.set_antenna('TX/RX', 0)
185 | else:
186 | logging.error("ERROR: Select a radio type!")
187 | sys.exit(1)
188 |
189 | source = msg_source()
190 |
191 | tb = auto_ber_test(samp_rate, source, sink, sw_gain, -100, interfering_offset)
192 | tb.start()
193 |
194 | pkt_data = numpy.random.bytes(255)
195 |
196 | lfr = Radio(port, baud)
197 |
198 | cfg = lfr.get_cfg()
199 | cfg['freq'] = int(lfr_freq)
200 | cfg['flags'] &= ~(1) # ~CRC_ENABLE
201 | lfr.set_cfg(cfg)
202 | print(json.dumps(lfr.get_cfg(), indent=4))
203 |
204 | # Grr... Python scoping...
205 | global timer
206 | timer = None
207 |
208 | global num_bits
209 | global num_errs
210 | num_bits = 0
211 | num_errs = 0
212 |
213 | def step():
214 | global num_bits
215 | global num_errs
216 | logging.info('')
217 | logging.info('Interfering Power: {: >3.1f} dBc Bits sent: {: >8} Bits Errors: {: >5} log10(BER)= {:.02f}'\
218 | .format(interfering_rel_gains[0], num_bits, num_errs, math.log10(num_errs + 0.001) - math.log10(num_bits + 0.001)))
219 |
220 | interfering_rel_gains[:] = interfering_rel_gains[1:]
221 | if not interfering_rel_gains:
222 | tb.stop()
223 | thread.interrupt_main()
224 | # Ugly, skips any finally block
225 | # Essentially crashes
226 | os._exit(0)
227 |
228 | logging.info('Stepping to power level: {} dBc'.format(interfering_rel_gains[0]))
229 | tb.set_interfering_gain(interfering_rel_gains[0] + sw_gain)
230 |
231 | num_bits = 0
232 | num_errs = 0
233 |
234 | for _ in range(num_pkts):
235 | source.transmit(pkt_data)
236 |
237 | global timer
238 | timer = Timer(num_pkts * ms_per_packet / 1000.0, step)
239 | timer.start()
240 |
241 | timer = Timer(num_pkts * ms_per_packet / 1000.0, step)
242 |
243 | logging.info('Starting at power level: {} dBc'.format(interfering_rel_gains[0]))
244 | tb.set_interfering_gain(interfering_rel_gains[0] + sw_gain)
245 |
246 | for _ in range(num_pkts):
247 | source.transmit(pkt_data)
248 |
249 | timer.start()
250 |
251 | while True:
252 | data = [ord(x) for x in lfr.rx()]
253 |
254 | if len(data) == len(pkt_data):
255 | for x, y in zip(data, pkt_data):
256 | xor = x ^ ord(y)
257 | for _ in range(8):
258 | num_bits += 1
259 | if (xor & 1):
260 | num_errs += 1
261 | xor >>= 1
262 |
263 |
264 | if __name__ == '__main__':
265 | main()
266 |
--------------------------------------------------------------------------------
/cmd_parser.c:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #include "cmd_parser.h"
20 | #include "cmd_handler.h"
21 |
22 | #include "error.h"
23 | #include "lfr.h"
24 |
25 | void command_handler(uint8_t cmd, uint8_t len, uint8_t* payload);
26 |
27 | bool validate_cmd(uint8_t cmd);
28 | bool validate_length(uint8_t cmd, uint8_t len);
29 | uint16_t fletcher(uint16_t old_checksum, uint8_t c);
30 |
31 |
32 | /**
33 | * enum for states of the byte parser state machine
34 | * Each state is named for the byte which the state machine expects to receive.
35 | */
36 | enum parser_state_e {S_SYNC0, S_SYNC1, S_CMD, S_PAYLOADLEN, S_PAYLOAD, S_CHECKSUM0, S_CHECKSUM1};
37 |
38 | /**
39 | * enum for the result of parsing a byte
40 | * The results can be:
41 | * wait for another character (take no action)
42 | * execute the command (if the byte completed a valid command)
43 | * invalid (the command was not valid, or the payload length was not valid for the command)
44 | * bad checksum (checksum mismatch)
45 | */
46 | enum parser_result_e {R_WAIT, R_ACT, R_INVALID, R_BADSUM};
47 |
48 | /* \fn parse_char(uint8_t c)
49 | * \brief Character-based command parser
50 | * \details Takes one character at a time, checks for validity, reports error or executes a completed command
51 | * \param c The next byte
52 | */
53 | void parse_char(uint8_t c) {
54 | static enum parser_state_e next_state = S_SYNC0;
55 | static enum parser_result_e result = R_WAIT;
56 | static uint8_t cmd;
57 | static uint8_t payload_len = 0, payload_counter = 0;
58 | static uint8_t payload[MAX_PAYLOAD_LEN];
59 | static uint16_t checksum;
60 | static uint16_t calc_checksum;
61 |
62 | /* step through the packet structure based on the latest character */
63 | switch (next_state) {
64 | case S_SYNC0:
65 | if (SYNCWORD_H == c) next_state = S_SYNC1;
66 | break;
67 | case S_SYNC1:
68 | if (SYNCWORD_L == c) next_state = S_CMD;
69 | /* for a sync word "Sy", "SSy" should be detected as valid */
70 | else if (SYNCWORD_H != c)next_state = S_SYNC0;
71 | break;
72 | case S_CMD:
73 | if (validate_cmd(c)) {
74 | cmd = c;
75 | //calc_checksum = fletcher(calc_checksum, c);
76 | calc_checksum = fletcher(0, c);
77 | next_state = S_PAYLOADLEN;
78 | } else {
79 | result = R_INVALID;
80 | }
81 | break;
82 | case S_PAYLOADLEN:
83 | if (validate_length(cmd, c)) {
84 | payload_len = c;
85 | payload_counter = 0;
86 | calc_checksum = fletcher(calc_checksum, c);
87 | if (payload_len) {
88 | next_state = S_PAYLOAD;
89 | } else {
90 | next_state = S_CHECKSUM0;
91 | }
92 | } else result = R_INVALID;
93 | break;
94 | case S_PAYLOAD:
95 | payload[payload_counter] = c;
96 | calc_checksum = fletcher(calc_checksum, c);
97 | payload_counter++;
98 | if (payload_counter == payload_len) {
99 | next_state = S_CHECKSUM0;
100 | }
101 | break;
102 | case S_CHECKSUM0:
103 | checksum = ((uint16_t) c) << 8;
104 | next_state = S_CHECKSUM1;
105 | break;
106 | case S_CHECKSUM1:
107 | checksum = checksum | (uint16_t)c;
108 | if (calc_checksum == checksum) {
109 | result = R_ACT;
110 | } else result = R_BADSUM;
111 | break;
112 | default:
113 | //error();
114 | break;
115 | }//switch(c)
116 |
117 | /* if that character created an error or concluded a valid packet, do something */
118 | switch (result) {
119 | case R_INVALID:
120 | next_state = S_SYNC0;
121 | result = R_WAIT;
122 | cmd_err(ECMDINVAL);
123 | break;
124 | case R_BADSUM:
125 | next_state = S_SYNC0;
126 | result = R_WAIT;
127 | cmd_err(ECMDBADSUM);
128 | break;
129 | case R_ACT:
130 | next_state = S_SYNC0;
131 | result = R_WAIT;
132 | command_handler(cmd, payload_len, payload);
133 | send_reply_to_host();
134 | case R_WAIT:
135 | break;
136 | }
137 | }
138 | /* returns true for valid command types, false for invalid */
139 | bool validate_cmd(uint8_t cmd) {
140 | switch (cmd) {
141 | case CMD_NOP:
142 | case CMD_RESET:
143 | case CMD_READ_TXPWR:
144 | case CMD_SET_TXPWR:
145 | case CMD_TXDATA:
146 | case CMD_TX_PSR:
147 | case CMD_TX_ABORT:
148 | case CMD_SET_FREQ:
149 | case CMD_GET_CFG:
150 | case CMD_SET_CFG:
151 | case CMD_SAVE_CFG:
152 | case CMD_CFG_DEFAULT:
153 | case CMD_GET_QUEUE_DEPTH:
154 | case CMD_GET_TEMPS:
155 | return true;
156 | default:
157 | return false;
158 | }
159 | }
160 |
161 | /* returns true if the payload length is valid for the command type, false if not */
162 | bool validate_length(uint8_t cmd, uint8_t len) {
163 | if (len > MAX_PAYLOAD_LEN) {
164 | return false;
165 | }
166 |
167 | switch (cmd) {
168 | case CMD_SET_TXPWR:
169 | return len == 2;
170 | case CMD_TXDATA:
171 | return len > 0;
172 | case CMD_SET_FREQ:
173 | return len == 4;
174 | case CMD_SET_CFG:
175 | return len > 0; // Allow any non-zero here, we check it in the cmd callback
176 | default:
177 | return len == 0;
178 | }
179 | }
180 |
181 | /* update the mod-256 Fletcher checksum with the byte c */
182 | uint16_t fletcher(uint16_t old_checksum, uint8_t c) {
183 | uint8_t lsb, msb;
184 | lsb = old_checksum;
185 | msb = (old_checksum >> 8) + c;
186 | lsb += msb;
187 | return ((uint16_t) msb<<8) | (uint16_t)lsb;
188 | }
189 |
190 | void command_handler(uint8_t cmd, uint8_t len, uint8_t* payload) {
191 |
192 | switch (cmd) {
193 | case CMD_NOP:
194 | cmd_nop();
195 | break;
196 | case CMD_RESET:
197 | cmd_reset();
198 | break;
199 | case CMD_READ_TXPWR:
200 | cmd_get_txpwr();
201 | break;
202 | case CMD_SET_TXPWR:
203 | cmd_set_txpwr((uint16_t) payload[0] << 8 | payload[1]);
204 | break;
205 | case CMD_TXDATA:
206 | cmd_tx_data(len, payload);
207 | break;
208 | case CMD_SET_FREQ:
209 | cmd_set_freq((uint32_t) payload[0] << 24 | (uint32_t) payload[1] << 16 | (uint32_t) payload[2] << 8 |
210 | payload[3]);
211 | break;
212 | case CMD_TX_PSR:
213 | cmd_tx_psr();
214 | break;
215 | case CMD_TX_ABORT:
216 | cmd_abort_tx();
217 | break;
218 | case CMD_GET_CFG:
219 | cmd_get_cfg();
220 | break;
221 | case CMD_SET_CFG:
222 | cmd_set_cfg(len, payload);
223 | break;
224 | case CMD_SAVE_CFG:
225 | cmd_save_cfg();
226 | break;
227 | case CMD_CFG_DEFAULT:
228 | cmd_cfg_default();
229 | break;
230 | case CMD_GET_QUEUE_DEPTH:
231 | cmd_get_queue_depth();
232 | break;
233 | case CMD_GET_TEMPS:
234 | cmd_get_temps();
235 | }
236 | }
237 |
238 | // Place to hold the current command reply
239 | uint8_t reply_command = CMD_INTERNALERR;
240 | uint8_t reply_buffer[255];
241 | uint8_t reply_length = 0;
242 |
243 | // Always goes to flight computer interface
244 | void internal_error(uint8_t code)
245 | {
246 | host_reply_putc(SYNCWORD_H);
247 | host_reply_putc(SYNCWORD_L);
248 | uint16_t chksum = 0;
249 | host_reply_putc(CMD_INTERNALERR);
250 | chksum = fletcher(chksum, CMD_INTERNALERR);
251 | host_reply_putc(1);
252 | chksum = fletcher(chksum, 1);
253 | host_reply_putc(code);
254 | chksum = fletcher(chksum, code);
255 | host_reply_putc((char) (chksum >> 8));
256 | host_reply_putc((char) chksum);
257 | }
258 |
259 | void send_reply_to_host()
260 | {
261 | host_reply_putc(SYNCWORD_H);
262 | host_reply_putc(SYNCWORD_L);
263 | uint16_t chksum = 0;
264 | host_reply_putc(reply_command);
265 | chksum = fletcher(chksum, reply_command);
266 |
267 | host_reply_putc(reply_length);
268 | chksum = fletcher(chksum, reply_length);
269 |
270 | uint8_t *payload = reply_buffer;
271 | int len = reply_length;
272 | for (; len > 0; len--) {
273 | host_reply_putc(*payload);
274 | chksum = fletcher(chksum, *payload);
275 | payload++;
276 | }
277 |
278 | host_reply_putc((char) (chksum >> 8));
279 | host_reply_putc((char) chksum);
280 | }
281 |
282 |
283 | void reply_cmd_error(uint8_t code)
284 | {
285 | reply_command = (CMD_REPLY | CMD_REPLYERR);
286 | reply_length = 1;
287 | reply_buffer[0] = code;
288 | }
289 |
290 | void reply(uint8_t cmd, int len, uint8_t *payload)
291 | {
292 | reply_command = (CMD_REPLY | cmd);
293 | reply_length = len;
294 | memcpy(reply_buffer, payload, len);
295 | }
296 |
--------------------------------------------------------------------------------
/cmd_handler.c:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #include
20 | #include
21 |
22 | #include "error.h"
23 | #include "lfr.h"
24 | #include "radio.h"
25 | #include "mcu.h"
26 | #include "lib446x/si446x.h"
27 | #include "cmd_handler.h"
28 | #include "cmd_parser.h"
29 | #include "msp430_uart.h"
30 | #include "settings.h"
31 | #include "status.h"
32 | #include "adc.h"
33 | #include "pins.h"
34 |
35 | void cmd_nop() {
36 | reply(CMD_NOP, 0, NULL);
37 | }
38 |
39 | void cmd_reset() {
40 | WDTCTL = 0x0000; // Force WDT reset
41 | }
42 |
43 | void cmd_set_txpwr(uint16_t pwr) {
44 | settings.tx_gate_bias = pwr;
45 | reply(CMD_SET_TXPWR, 0, NULL);
46 | }
47 |
48 | void cmd_get_txpwr() {
49 | uint8_t resp[] = {(uint8_t)(settings.tx_gate_bias >> 8), (uint8_t)(settings.tx_gate_bias & 0xFF)};
50 | reply(CMD_READ_TXPWR, sizeof(resp), resp);
51 | }
52 |
53 | void cmd_tx_data(int len, uint8_t *data) {
54 | int err;
55 |
56 | err = pkt_buf_enqueue(&tx_queue, len, data);
57 |
58 | if (err) {
59 | reply_cmd_error((uint8_t) -err);
60 | } else {
61 | if (!get_status(STATUS_TXBUSY)) {
62 | set_status(STATUS_TXBUSY, true);
63 | pre_transmit();
64 |
65 | uint16_t pkt_len = MAX_PAYLOAD_LEN;
66 | err = pkt_buf_dequeue(&tx_queue, (int*)&pkt_len, buf);
67 |
68 | if (err) {
69 | reply_cmd_error((uint8_t) -err);
70 | return;
71 | }
72 |
73 | err = send_w_retry(pkt_len, buf);
74 |
75 | if (err) {
76 | reply_cmd_error((uint8_t) -err);
77 | } else {
78 | reply(CMD_TXDATA, 0, NULL);
79 | }
80 | } else {
81 | reply(CMD_TXDATA, 0, NULL);
82 | }
83 | }
84 | }
85 |
86 | void cmd_get_queue_depth() {
87 | uint16_t depth = pkt_buf_depth(&tx_queue);
88 | uint8_t data[] = {depth >> 8, depth & 0xFF};
89 | reply(CMD_GET_QUEUE_DEPTH, sizeof(data), data);
90 | }
91 |
92 | void cmd_set_freq(uint32_t freq) {
93 | settings.freq = freq;
94 | int err = set_frequency(freq);
95 |
96 | if (err) {
97 | reply_cmd_error((uint8_t) -err);
98 | } else {
99 | reply(CMD_SET_FREQ, 0, NULL);
100 | }
101 | }
102 |
103 | void cmd_abort_tx()
104 | {
105 |
106 | int err;
107 |
108 | err = post_transmit();
109 |
110 | if (err) {
111 | reply_cmd_error((uint8_t) -err);
112 | return;
113 | }
114 |
115 | err = si446x_idle(&dev);
116 | if (err) {
117 | reply_cmd_error((uint8_t) -err);
118 | return;
119 | }
120 |
121 | err = set_modem_config(settings.modem_config);
122 |
123 | if (err) {
124 | reply_cmd_error((uint8_t) -err);
125 | return;
126 | }
127 |
128 | if ((settings.flags & FLAG_MOD_MASK) == FLAG_MOD_CW) {
129 | err = si446x_set_mod_type(&dev, MOD_TYPE_CW);
130 | } else if ((settings.flags & FLAG_MOD_MASK) == FLAG_MOD_FSK) {
131 | err = si446x_set_mod_type(&dev, MOD_TYPE_2FSK);
132 | } else if ((settings.flags & FLAG_MOD_MASK) == FLAG_MOD_GFSK) {
133 | err = si446x_set_mod_type(&dev, MOD_TYPE_2GFSK);
134 | } else {
135 | err = si446x_set_mod_type(&dev, MOD_TYPE_2GFSK);
136 | }
137 |
138 | if (err) {
139 | reply_cmd_error((uint8_t) -err);
140 | return;
141 | }
142 |
143 | err = si446x_recv_async(&dev, 255, buf, rx_cb);
144 |
145 | if (err) {
146 | reply_cmd_error((uint8_t) -err);
147 | } else {
148 | reply(CMD_TX_ABORT, 0, NULL);
149 | }
150 | }
151 |
152 | void cmd_tx_psr()
153 | {
154 | int err;
155 | int attempts;
156 |
157 | if ((settings.flags & FLAG_MOD_MASK) == FLAG_MOD_CW) {
158 | err = si446x_set_mod_type(&dev, MOD_SRC_RAND | MOD_TYPE_CW);
159 | } else if ((settings.flags & FLAG_MOD_MASK) == FLAG_MOD_FSK) {
160 | err = si446x_set_mod_type(&dev, MOD_SRC_RAND | MOD_TYPE_2FSK);
161 | } else if ((settings.flags & FLAG_MOD_MASK) == FLAG_MOD_GFSK) {
162 | err = si446x_set_mod_type(&dev, MOD_SRC_RAND | MOD_TYPE_2GFSK);
163 | } else {
164 | err = si446x_set_mod_type(&dev, MOD_SRC_RAND | MOD_TYPE_2GFSK);
165 | }
166 |
167 | if (err) {
168 | reply_cmd_error((uint8_t) -err);
169 | return;
170 | }
171 |
172 | err = pre_transmit();
173 | if (err) {
174 | reply_cmd_error((uint8_t) -err);
175 | return;
176 | }
177 |
178 | for (attempts = 0; attempts < 3; attempts++) {
179 | err = si446x_fire_tx(&dev);
180 |
181 | if (err == -ERESETSI) {
182 |
183 | err = reset_si446x();
184 | if (err) {
185 | reply_cmd_error((uint8_t) -err);
186 | return;
187 | }
188 |
189 | // Retry
190 |
191 | } else if (err) {
192 | reply_cmd_error((uint8_t) -err);
193 | return;
194 | } else {
195 | reply(CMD_TX_PSR, 0, NULL);
196 | return;
197 | }
198 | }
199 |
200 | // Give up
201 | reply_cmd_error((uint8_t) ETIMEOUT);
202 | }
203 |
204 | void cmd_set_cfg(int len, uint8_t *data)
205 | {
206 | unsigned int i = 0;
207 |
208 | // Right length?
209 | if (len != 26) {
210 | cmd_err(ECMDINVAL);
211 | return;
212 | }
213 |
214 | // Check cfg struct version
215 | if (data[i++] != SETTINGS_VER) {
216 | cmd_err(EINVAL);
217 | return;
218 | }
219 |
220 | // Okay, we've got the correct length and version!
221 | // Set the settings!
222 |
223 | settings.freq = ((uint32_t)data[i] << 24) | ((uint32_t)data[i+1] << 16) | ((uint32_t)data[i+2] << 8) | data[i+3];
224 | i += 4;
225 |
226 | settings.modem_config = data[i];
227 | i += 1;
228 |
229 | settings.tcxo_vpull = (data[i] << 8) | data[i+1];
230 | i += 2;
231 |
232 | settings.tx_gate_bias = (data[i] << 8) | data[i+1];
233 | i += 2;
234 |
235 | settings.tx_vdd = (data[i] << 8) | data[i+1];
236 | i += 2;
237 |
238 | settings.pa_ilimit = (data[i] << 8) | data[i+1];
239 | i += 2;
240 |
241 | settings.tx_vdd_delay = (data[i] << 8) | data[i+1];
242 | i += 2;
243 |
244 | settings.flags = (data[i] << 8) | data[i+1];
245 | i += 2;
246 |
247 | unsigned int j;
248 | for (j = 0; j < 8; j++) {
249 | settings.callsign[j] = data[i++];
250 | }
251 |
252 | int err = reload_config();
253 |
254 | if (err) {
255 | reply_cmd_error((uint8_t) -err);
256 | } else {
257 | reply(CMD_SET_CFG, 0, NULL);
258 | }
259 |
260 | }
261 |
262 | void cmd_get_cfg()
263 | {
264 | uint8_t data[] = {
265 | SETTINGS_VER,
266 |
267 | settings.freq >> 24,
268 | (settings.freq >> 16) & 0xFF,
269 | (settings.freq >> 8) & 0xFF,
270 | settings.freq & 0xFF,
271 |
272 | settings.modem_config,
273 |
274 | settings.tcxo_vpull >> 8,
275 | settings.tcxo_vpull & 0xFF,
276 |
277 | settings.tx_gate_bias >> 8,
278 | settings.tx_gate_bias & 0xFF,
279 |
280 | settings.tx_vdd >> 8,
281 | settings.tx_vdd & 0xFF,
282 |
283 | settings.pa_ilimit >> 8,
284 | settings.pa_ilimit & 0xFF,
285 |
286 | settings.tx_vdd_delay >> 8,
287 | settings.tx_vdd_delay & 0xFF,
288 |
289 | settings.flags >> 8,
290 | settings.flags & 0xFF,
291 |
292 | settings.callsign[0],
293 | settings.callsign[1],
294 | settings.callsign[2],
295 | settings.callsign[3],
296 | settings.callsign[4],
297 | settings.callsign[5],
298 | settings.callsign[6],
299 | settings.callsign[7]
300 | };
301 |
302 | reply(CMD_GET_CFG, sizeof(data), data);
303 | }
304 |
305 | void cmd_save_cfg()
306 | {
307 | int err;
308 | err = settings_save();
309 | if (err) {
310 | reply_cmd_error((uint8_t) -err);
311 | } else {
312 | reply(CMD_SAVE_CFG, 0, NULL);
313 | }
314 | }
315 |
316 | void cmd_cfg_default()
317 | {
318 | int err;
319 | err = settings_load_default();
320 | if (err) {
321 | reply_cmd_error((uint8_t) -err);
322 | } else {
323 | reload_config();
324 | reply(CMD_CFG_DEFAULT, 0, NULL);
325 | }
326 | }
327 |
328 | //Calibration values for internal temp sensor from TLV
329 | #define ADC_2V5_30C_CAL (*(int*) 0x01A22)
330 | #define ADC_2V5_85C_CAL (*(int*) 0x01A24)
331 |
332 | void cmd_get_temps(){
333 | int32_t mcu_temp_scale = (8500L-3000L)/(ADC_2V5_85C_CAL - ADC_2V5_30C_CAL);
334 | int16_t mcu_temp = 0;
335 | int16_t pa_temp = 0;
336 | mcu_temp = adc_read(ADC_CHAN_TEMP);
337 | pa_temp = adc_read(PA_TEMP_ACHAN);
338 | mcu_temp = 3000+(mcu_temp - ADC_2V5_30C_CAL) * mcu_temp_scale;
339 | //TMP235, 10mV/C, 500mV at 0C, so 4095 means 2.5V means 200C, 0 means 0V means -50C
340 | pa_temp =((pa_temp * 25000L)>>12) - 5000;
341 | uint8_t data[4] = {
342 | pa_temp >> 8,
343 | pa_temp & 0xFF,
344 |
345 | mcu_temp >> 8,
346 | mcu_temp & 0xFF
347 | };
348 | reply(CMD_GET_TEMPS, 4, (uint8_t*) data);
349 | }
350 |
351 | void cmd_err(int err) {
352 | reply_cmd_error((uint8_t) err);
353 | }
354 |
355 | int host_reply_putc(uint8_t c) {
356 | uart_putc(c);
357 | return 0;
358 | }
359 |
--------------------------------------------------------------------------------
/lfr.c:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * Copyright (C) 2018 Grant Iraci, Brian Bezanson
3 | * A project of the University at Buffalo Nanosatellite Laboratory
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | #include
20 | #include
21 | #include
22 | #include "pins.h"
23 | #include "error.h"
24 | #include "lib446x/si446x.h"
25 | #include "mcu.h"
26 | #include "radio.h"
27 | #include "amp.h"
28 | #include "msp430_uart.h"
29 | #include "cmd_parser.h"
30 | #include "settings.h"
31 | #include "pkt_buf.h"
32 | #include "status.h"
33 | #include "adc.h"
34 |
35 | // Backing buffer for tx_queue
36 | uint8_t __attribute__((persistent)) tx_backing_buf[16384] = {0};
37 |
38 | uint8_t buf[255];
39 |
40 | struct si446x_device dev;
41 | struct pkt_buf tx_queue;
42 |
43 | volatile bool do_pong = false;
44 | volatile bool radio_irq = true;
45 |
46 | void tx_cb(struct si446x_device *dev, int err);
47 | void rx_cb(struct si446x_device *dev, int err, int len, uint8_t *data);
48 | int reset_si446x();
49 |
50 | int pre_transmit_no_delay()
51 | {
52 | int err;
53 | gpio_write(PA_PWR_EN_PIN, HIGH);
54 | err = set_current_limit(settings.pa_ilimit);
55 | if(err) return err;
56 | err = set_drain_voltage(settings.tx_vdd);
57 | if(err) return err;
58 | err = set_gate_bias(settings.tx_gate_bias);
59 | if(err) return err;
60 | //VGG DRIVE FIX TEST:
61 | gpio_write(TX_ACT_PIN, HIGH);
62 | gpio_config(TX_ACT_PIN, OUTPUT);
63 | return 0;
64 | }
65 |
66 | int pre_transmit()
67 | {
68 | int err;
69 | err = pre_transmit_no_delay();
70 | if(err) return err;
71 | delay_micros(settings.tx_vdd_delay);
72 | return 0;
73 | }
74 |
75 | int pa_power_keepalive(){
76 | //Pulse the PA_PWR_EN pin low to recharge the RC filter cap
77 | int err;
78 | //cut the drain current by killing the gate bias
79 | gpio_config(TX_ACT_PIN, INPUT);
80 | err = set_gate_bias(0);
81 | if(err) return err;
82 | gpio_write(PA_PWR_EN_PIN, LOW);
83 | //turn the gate bias back on
84 | //use the I2C transaction period as the delay to recharge the cap
85 | err = set_gate_bias(settings.tx_gate_bias);
86 | if(err) return err;
87 | gpio_write(PA_PWR_EN_PIN, HIGH);
88 | gpio_config(TX_ACT_PIN, OUTPUT);
89 | return 0;
90 | }
91 |
92 | int post_transmit()
93 | {
94 | int err;
95 | gpio_write(PA_PWR_EN_PIN, LOW);
96 | //VGG DRIVE FIX TEST:
97 | gpio_config(TX_ACT_PIN, INPUT);
98 | err = set_gate_bias(0);
99 | if(err) return err;
100 | err = set_drain_voltage(0);
101 | if(err) return err;
102 | err = set_current_limit(0);
103 | if(err) return err;
104 | return 0;
105 | }
106 |
107 | int send_w_retry(int len, uint8_t *buf)
108 | {
109 | int attempts;
110 | int err;
111 |
112 | si446x_idle(&dev);
113 | pre_transmit();
114 |
115 | for (attempts = 0; attempts < 3; attempts++) {
116 |
117 | err = si446x_send_async(&dev, len, buf, tx_cb);
118 |
119 | if (err == -ERESETSI) {
120 |
121 | err = reset_si446x();
122 | if (err) {
123 | return err;
124 | }
125 |
126 | // Retry
127 | printf("Si446x BUG: Resetting...\n");
128 |
129 | } else if (err) {
130 | return err;
131 | } else {
132 | return ESUCCESS;
133 | }
134 | }
135 |
136 | // Give up
137 | return -ETIMEOUT;
138 | }
139 |
140 | void tx_cb(struct si446x_device *dev, int err)
141 | {
142 | if (err) {
143 | internal_error((uint8_t) -err);
144 | }
145 |
146 | wdt_feed();
147 |
148 | if (pkt_buf_depth(&tx_queue) > 0) {
149 | int pkt_len = MAX_PAYLOAD_LEN;
150 | err = pkt_buf_dequeue(&tx_queue, &pkt_len, buf);
151 |
152 | if (err) {
153 | internal_error((uint8_t) -err);
154 | post_transmit();
155 | set_status(STATUS_TXBUSY, false);
156 | si446x_recv_async(dev, 255, buf, rx_cb);
157 | return;
158 | }
159 |
160 | pa_power_keepalive();
161 | err = send_w_retry(pkt_len, buf);
162 |
163 | if (err) {
164 | internal_error((uint8_t) -err);
165 | post_transmit();
166 | set_status(STATUS_TXBUSY, false);
167 | si446x_recv_async(dev, 255, buf, rx_cb);
168 | return;
169 | }
170 | } else {
171 | post_transmit();
172 | set_status(STATUS_TXBUSY, false);
173 | si446x_recv_async(dev, 255, buf, rx_cb);
174 | }
175 | }
176 |
177 | void rx_cb(struct si446x_device *dev, int err, int len, uint8_t *data)
178 | {
179 |
180 | // Stop rx timeout timer
181 | TA1CCTL0 = 0; // TACCR0 interrupt disabled
182 | TA1CTL |= TACLR; // Stop timer
183 |
184 | if (err) {
185 | set_status(STATUS_TXBUSY, false);
186 | internal_error((uint8_t) -err);
187 | si446x_recv_async(dev, 255, buf, rx_cb);
188 | return;
189 | }
190 |
191 | if (len == 4 && data[0] == 'P' && data[1] == 'I' && data[2] == 'N'
192 | && data[3] == 'G') {
193 |
194 | uint8_t resp[] = {
195 | 'P',
196 | 'O',
197 | 'N',
198 | 'G',
199 | };
200 | set_status(STATUS_TXBUSY, true);
201 | gpio_write(TX_ACT_PIN, HIGH);
202 | err = set_gate_bias(settings.tx_gate_bias);
203 | si446x_setup_tx(dev, sizeof(resp), resp, tx_cb);
204 | do_pong = true;
205 |
206 | return;
207 | } else {
208 | // Handle RX'd packet
209 | reply(CMD_RXDATA, len, data);
210 | }
211 | set_status(STATUS_TXBUSY, false);
212 | si446x_recv_async(dev, 255, buf, rx_cb);
213 | send_reply_to_host();
214 | }
215 |
216 | void error(int err, char *file, int line) {
217 | printf("Error: %s (%d) at %s:%d\n", "" /* strerror[err] */ , err, file, line);
218 | gpio_config(0x40, OUTPUT);
219 | gpio_write(0x40, HIGH);
220 | __disable_interrupt();
221 | while (1) LPM0;
222 | }
223 |
224 | int reload_config()
225 | {
226 | int err;
227 |
228 | err = set_frequency(settings.freq);
229 | if (err) {
230 | return err;
231 | }
232 |
233 | err = set_dac_output(TCXO_CHAN, settings.tcxo_vpull);
234 | if (err) {
235 | return err;
236 | }
237 |
238 | err = si446x_check_crc(&dev, settings.flags & FLAG_CRC_CHECK);
239 | if (err) {
240 | return err;
241 | }
242 |
243 | err = si446x_data_whitening(&dev, settings.flags & FLAG_WHITEN);
244 | if (err) {
245 | return err;
246 | }
247 |
248 | err = set_modem_config(settings.modem_config);
249 | if (err) {
250 | return err;
251 | }
252 |
253 | if ((settings.flags & FLAG_MOD_MASK) == FLAG_MOD_CW) {
254 | err = si446x_set_mod_type(&dev, MOD_TYPE_CW);
255 | } else if ((settings.flags & FLAG_MOD_MASK) == FLAG_MOD_FSK) {
256 | err = si446x_set_mod_type(&dev, MOD_TYPE_2FSK);
257 | } else if ((settings.flags & FLAG_MOD_MASK) == FLAG_MOD_GFSK) {
258 | err = si446x_set_mod_type(&dev, MOD_TYPE_2GFSK);
259 | } else {
260 | err = si446x_set_mod_type(&dev, MOD_TYPE_2GFSK);
261 | }
262 |
263 | if (err) {
264 | return err;
265 | }
266 |
267 | return 0;
268 | }
269 |
270 | int config_si446x()
271 | {
272 | int err;
273 |
274 | err = reload_config();
275 |
276 | if (err) {
277 | settings_load_default();
278 | // TODO: Report this error
279 | }
280 |
281 | err = si446x_config_crc(&dev, CRC_SEED_1 | CRC_CCIT_16);
282 | if (err) {
283 | return err;
284 | }
285 |
286 | //err = si446x_set_tx_pwr(&dev, 0x14);
287 | err = si446x_set_tx_pwr(&dev, 0x7f);
288 | if (err) {
289 | return err;
290 | }
291 |
292 | err = si446x_cfg_gpio(&dev, GPIO_SYNC_WORD_DETECT, GPIO_RX_STATE, GPIO_TX_DATA_CLK, GPIO_TX_STATE);
293 | if (err) {
294 | return err;
295 | }
296 |
297 | return ESUCCESS;
298 | }
299 |
300 | int reset_si446x()
301 | {
302 |
303 | int err;
304 |
305 | // Disable pin interrupts while we reset
306 | disable_pin_interrupt(GPIO0);
307 | disable_pin_interrupt(INT_PIN);
308 |
309 | err = si446x_reinit(&dev);
310 |
311 | if (err) {
312 | return err;
313 | }
314 |
315 | err = config_si446x();
316 | if (err) {
317 | return err;
318 | }
319 |
320 | enable_pin_interrupt(GPIO0, RISING);
321 | enable_pin_interrupt(INT_PIN, FALLING);
322 |
323 | return ESUCCESS;
324 |
325 | }
326 |
327 | int main(void)
328 | {
329 | int err;
330 | WDTCTL = WDTPW | WDTHOLD; // stop watchdog timer
331 |
332 | settings_load_saved();
333 | pkt_buf_init(&tx_queue, sizeof(tx_backing_buf), tx_backing_buf);
334 | mcu_init();
335 | uart_init();
336 |
337 | gpio_config(TX_ACT_PIN, OUTPUT);
338 | gpio_write(TX_ACT_PIN, LOW);
339 | gpio_config(PA_PWR_EN_PIN, OUTPUT);
340 | gpio_write(PA_PWR_EN_PIN, LOW);
341 |
342 | //Make sure the DAC has been reset
343 | gpio_config(DAC_nPWR_PIN, OUTPUT);
344 | gpio_write(DAC_nPWR_PIN, HIGH);
345 | gpio_config(SDA_PIN, OUTPUT); //Drive I2C pins low to discharge the cap quickly
346 | gpio_config(SCL_PIN, OUTPUT);
347 | gpio_write(SDA_PIN, LOW);
348 | gpio_write(SCL_PIN, LOW);
349 | delay_micros(10000); //wait for the 3V3 DAC cap to discharge fully
350 | gpio_config(SDA_PIN, INPUT);
351 | gpio_config(SCL_PIN, INPUT);
352 | gpio_write(DAC_nPWR_PIN, LOW); //turn the DAC back on
353 |
354 | //Power-on tests
355 | printf("LFR Starting up...\n");
356 | printf("LFR Build: %s\n", board_info.sw_ver);
357 | set_status(STATUS_RESET, true);
358 | reply(CMD_RESET, 0, NULL);
359 | send_reply_to_host();
360 |
361 | i2c_init();
362 | err = set_gate_bias(0x000);
363 |
364 | if (err) {
365 | error(-err, __FILE__, __LINE__);
366 | return err;
367 | }
368 |
369 | si446x_create(&dev, NSEL_PIN, SDN_PIN, INT_PIN, XTAL_FREQ, OSC_TYPE);
370 |
371 | err = si446x_init(&dev);
372 | if (err) {
373 | error(-err, __FILE__, __LINE__);
374 | return err;
375 | }
376 |
377 | config_si446x();
378 |
379 | printf("Successfully initialized Si446x!\n");
380 |
381 | enable_pin_interrupt(GPIO0, RISING);
382 | enable_pin_interrupt(INT_PIN, FALLING);
383 |
384 | err = si446x_recv_async(&dev, 255, buf, rx_cb);
385 |
386 | if (err) {
387 | error(-err, __FILE__, __LINE__);
388 | return err;
389 | }
390 |
391 | // Main loop
392 | while (true) {
393 |
394 | if (radio_irq) {
395 | radio_irq = false;
396 | err = si446x_update(&dev);
397 | if (err) {
398 | printf("Err: %d", err);
399 | // TODO: Better error handling
400 | si446x_reinit(&dev);
401 | config_si446x();
402 | si446x_recv_async(&dev, 255, buf, rx_cb);
403 | internal_error(-err);
404 | }
405 | } else if(uart_available()) {
406 | char c = uart_getc();
407 | parse_char(c);
408 | }
409 | // Don't bother sleeping
410 | }
411 | }
412 |
413 | #pragma vector=PORT5_VECTOR
414 | __interrupt void irq_isr()
415 | {
416 | P5IFG = 0;
417 | radio_irq = true;
418 | LPM4_EXIT; //This clears all the LPM bits, so it will leave the chip in run mode after the ISR
419 | }
420 |
421 | #pragma vector=PORT2_VECTOR
422 | __interrupt void sync_word_isr()
423 | {
424 | P2IFG &= ~(1 << (GPIO0 & 0x0F));
425 | TA0CTL |= TACLR; // Clear count
426 | TA0CTL |= MC__UP; // Start timer in UP mode
427 | TA0CCTL0 = CCIE; // TACCR0 interrupt enabled
428 |
429 | // Start RX timeout timer
430 | TA1CTL |= TACLR; // Clear count
431 | TA1CTL |= MC__UP; // Start timer in UP mode
432 | TA1CCTL0 = CCIE; // TACCR0 interrupt enabled
433 | }
434 |
435 | #pragma vector=TIMER0_A0_VECTOR
436 | __interrupt void ping_timer_isr()
437 | {
438 | TA0CCTL0 = 0; // TACCR0 interrupt disabled
439 | TA0CTL |= TACLR; // Stop timer
440 |
441 | if (do_pong) {
442 | do_pong = false;
443 | if (si446x_fire_tx(&dev) == -ERESETSI) {
444 | reset_si446x();
445 | si446x_recv_async(&dev, 255, buf, rx_cb);
446 | }
447 | }
448 | }
449 |
450 | #pragma vector=TIMER1_A0_VECTOR
451 | __interrupt void rx_timeout_isr()
452 | {
453 | TA1CCTL0 = 0; // TACCR0 interrupt disabled
454 | TA1CTL |= TACLR; // Stop timer
455 |
456 | si446x_rx_timeout(&dev);
457 | radio_irq = true;
458 | LPM4_EXIT; //This clears all the LPM bits, so it will leave the chip in run mode after the ISR
459 | }
460 |
--------------------------------------------------------------------------------
/util/com_radio.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import socket
5 | from time import sleep
6 | import threading
7 |
8 | import serial
9 | import struct
10 | import json
11 |
12 | from enum import Enum
13 |
14 | from random import randint
15 |
16 | SYNCWORD_H = 0xBE
17 | SYNCWORD_L = 0xEF
18 |
19 | class ParseState(Enum):
20 | SYNC_H = 0
21 | SYNC_L = 1
22 | CMD = 3
23 | PAYLOAD_LEN = 4
24 | PAYLOAD = 5
25 | CHKSUM_H = 6
26 | CHKSUM_L = 7
27 |
28 | class Command(Enum):
29 | NOP = 0x00
30 | RESET = 0x01
31 |
32 | TXDATA = 0x10
33 | RXDATA = 0x11
34 | TX_ABORT = 0x12
35 | TX_PSR = 0x13
36 |
37 | GET_CFG = 0x20
38 | SET_CFG = 0x21
39 | SAVE_CFG = 0x22
40 | CFG_DEFAULT = 0x23
41 | SET_FREQ = 0x24
42 | GET_TXPWR = 0x25
43 | SET_TXPWR = 0x26
44 |
45 | CMD_GET_QUEUE_DEPTH = 0x32
46 | GET_TEMPS = 0x33
47 |
48 | INTERNALERROR = 0x7E
49 | ERROR = 0x7F
50 |
51 | REPLY = 0x80
52 |
53 | class RadioException(Exception):
54 |
55 | def __init__(self, code):
56 | self.code = code
57 |
58 | if code == 0:
59 | self.error = 'ESUCCESS'
60 | self.msg = 'command succeeded'
61 | elif code == 1:
62 | self.error = 'ETIMEOUT'
63 | self.msg = 'timeout waiting for CTS'
64 | elif code == 2:
65 | self.error = 'EWRONGPART'
66 | self.msg = 'unsupported part number'
67 | elif code == 3:
68 | self.error = 'EINVAL'
69 | self.msg = 'invalid parameter'
70 | elif code == 4:
71 | self.error = 'EINVALSTATE'
72 | self.msg = 'invalid internal state'
73 | elif code == 5:
74 | self.error = 'ETOOLONG'
75 | self.msg = 'packet too long'
76 | elif code == 6:
77 | self.error = 'ECHKSUM'
78 | self.msg = 'invalid checksum'
79 | elif code == 7:
80 | self.error = 'EBUSY'
81 | self.msg = 'pending operation'
82 | elif code == 8:
83 | self.error = 'ESILICON'
84 | self.msg = 'Si446x silicon bug (zero len bug?); Try again'
85 | elif code == 9:
86 | self.error = 'ERESETSI'
87 | self.msg = 'Si446x silicon bug; Reset and try again'
88 | elif code == 11:
89 | self.error = 'EOVERFLOW'
90 | self.msg = 'Buffer full'
91 | elif code == 12:
92 | self.error = 'EUNDERFLOW'
93 | self.msg = 'Buffer empty'
94 | elif code == 19:
95 | self.error = 'ECMDINVAL'
96 | self.msg = 'Not a valid command'
97 | elif code == 22:
98 | self.error = 'ECMDBADSUM'
99 | self.msg = 'Bad command checksum'
100 | elif code == 127:
101 | self.error = 'ENOTIMPL'
102 | self.msg = 'Feature not implemented'
103 | else:
104 | self.error = 'UNKNOWN'
105 | self.msg = 'An unknown error occurred (' + str(code) + ')'
106 |
107 | def __str__(self):
108 | return 'RadioException(' + self.error + '): ' + self.msg
109 |
110 | pass
111 |
112 |
113 | class Radio:
114 |
115 | def __init__(self, port, baud=115200):
116 | self.ser = serial.Serial(port, baud)
117 | self.state = ParseState.SYNC_H
118 |
119 | def flush_serial(self):
120 | self.ser.flushOutput()
121 | self.ser.flushInput()
122 | self.ser.reset_input_buffer()
123 |
124 | def nop_flush(self, num):
125 | for _ in range(num):
126 | self.send_pkt(Command.NOP)
127 |
128 | def checksum(self, data):
129 | def feltcher(chksum, byte):
130 | lsb = chksum & 0xFF
131 | msb = chksum >> 8
132 | msb += byte
133 | msb &= 0xFF
134 | lsb += msb
135 | lsb &= 0xFF
136 | return (msb << 8) | lsb
137 |
138 | chksum = 0
139 | for x in data:
140 | chksum = feltcher(chksum, x)
141 | return chksum
142 |
143 | def send_pkt(self, cmd, data=bytes()):
144 |
145 | pkt = bytes([cmd.value, len(data)])
146 | pkt += data
147 | chksum = self.checksum(pkt)
148 | pkt += bytes([chksum >> 8, chksum & 0xFF])
149 | pkt = b'\xBE\xEF' + pkt
150 |
151 | self.ser.write(pkt)
152 |
153 | def reset(self):
154 | self.send_pkt(Command.RESET)
155 | (cmd, pay) = self.recv()
156 |
157 | if cmd == Command.ERROR.value | Command.REPLY.value:
158 | err = RadioException(pay[0])
159 | raise err
160 | elif cmd == Command.RESET.value | Command.REPLY.value:
161 | return
162 | else:
163 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
164 |
165 | def nop(self):
166 | self.send_pkt(Command.NOP)
167 | (cmd, pay) = self.recv()
168 |
169 | if cmd == Command.ERROR.value | Command.REPLY.value:
170 | err = RadioException(pay[0])
171 | raise err
172 | elif cmd == Command.NOP.value | Command.REPLY.value:
173 | return
174 | else:
175 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
176 |
177 | def tx_psr(self):
178 | self.send_pkt(Command.TX_PSR)
179 | (cmd, pay) = self.recv()
180 |
181 | if cmd == Command.ERROR.value | Command.REPLY.value:
182 | err = RadioException(pay[0])
183 | raise err
184 | elif cmd == Command.TX_PSR.value | Command.REPLY.value:
185 | return
186 | else:
187 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
188 |
189 | def tx_abort(self):
190 | self.send_pkt(Command.TX_ABORT)
191 | (cmd, pay) = self.recv()
192 |
193 | if cmd == Command.ERROR.value | Command.REPLY.value:
194 | err = RadioException(pay[0])
195 | raise err
196 | elif cmd == Command.TX_ABORT.value | Command.REPLY.value:
197 | return
198 | else:
199 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
200 |
201 | def set_freq(self, freq):
202 | self.send_pkt(Command.SET_FREQ, struct.pack("!I", freq))
203 | (cmd, pay) = self.recv()
204 |
205 | if cmd == Command.ERROR.value | Command.REPLY.value:
206 | err = RadioException(pay[0])
207 | raise err
208 | elif cmd == Command.SET_FREQ.value | Command.REPLY.value:
209 | return
210 | else:
211 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
212 |
213 | def set_txpwr(self, pwr):
214 | self.send_pkt(Command.SET_TXPWR, struct.pack("!H", pwr))
215 | (cmd, pay) = self.recv()
216 |
217 | if cmd == Command.ERROR.value | Command.REPLY.value:
218 | err = RadioException(pay[0])
219 | raise err
220 | elif cmd == Command.SET_TXPWR.value | Command.REPLY.value:
221 | return
222 | else:
223 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
224 |
225 | def tx(self, data):
226 | self.send_pkt(Command.TXDATA, data if isinstance(data, bytes) else data.encode('utf-8'))
227 | (cmd, pay) = self.recv()
228 |
229 | if cmd == Command.ERROR.value | Command.REPLY.value:
230 | err = RadioException(pay[0])
231 | if err.error == 'EOVERFLOW':
232 | sleep(0.05)
233 | self.tx(data)
234 | else:
235 | raise err
236 |
237 | elif cmd == Command.TXDATA.value | Command.REPLY.value:
238 | return
239 | else:
240 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
241 |
242 | def rx(self):
243 | (cmd, pay) = self.recv()
244 |
245 | if cmd == Command.ERROR.value | Command.REPLY.value:
246 | raise RadioException(pay[0])
247 | elif cmd == Command.RXDATA.value | Command.REPLY.value:
248 | return pay.decode("utf-8")
249 | else:
250 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
251 |
252 | def recv(self):
253 |
254 | payload = b''
255 |
256 | while True:
257 | c = self.ser.read(1)[0]
258 |
259 | if self.state is ParseState.SYNC_H:
260 | if c == SYNCWORD_H:
261 | self.state = ParseState.SYNC_L
262 | elif self.state is ParseState.SYNC_L:
263 | if c == SYNCWORD_L:
264 | self.state = ParseState.CMD
265 | elif c == SYNCWORD_H:
266 | self.state = ParseState.SYNC_L
267 | else:
268 | self.state = ParseState.SYNC_H
269 | elif self.state is ParseState.CMD:
270 | cmd = c
271 | self.state = ParseState.PAYLOAD_LEN
272 | elif self.state is ParseState.PAYLOAD_LEN:
273 | length = c
274 | # TODO: Validate len for cmd
275 | if (length):
276 | self.state = ParseState.PAYLOAD
277 | else:
278 | chksum = self.checksum(bytes([cmd, 0]))
279 | self.state = ParseState.CHKSUM_H
280 |
281 | elif self.state is ParseState.PAYLOAD:
282 | payload += bytes([c])
283 | length -= 1
284 | self.state = ParseState.PAYLOAD
285 | if (length == 0):
286 | chksum = self.checksum(bytes([cmd, len(payload)]) + payload)
287 | self.state = ParseState.CHKSUM_H
288 | elif self.state is ParseState.CHKSUM_H:
289 | if (c == chksum >> 8):
290 | self.state = ParseState.CHKSUM_L
291 | else:
292 | # TODO: Handle error
293 | pass
294 | self.state = ParseState.SYNC_H
295 | break
296 | elif self.state is ParseState.CHKSUM_L:
297 | if (c != chksum & 0xFF):
298 | # TODO: Handle error
299 | pass
300 | self.state = ParseState.SYNC_H
301 | break
302 |
303 |
304 | return (cmd, payload)
305 |
306 | def get_cfg(self):
307 | self.send_pkt(Command.GET_CFG)
308 |
309 | (cmd, pay) = self.recv()
310 |
311 | if cmd == (Command.ERROR.value | Command.REPLY.value):
312 | err = RadioException(pay[0])
313 | raise err
314 |
315 | elif cmd == (Command.GET_CFG.value | Command.REPLY.value):
316 |
317 | cfg = {}
318 | cfg['cfg_ver'], cfg['freq'], cfg['modem_config'], \
319 | cfg['txco_vpull'], cfg['tx_gate_bias'], cfg['tx_vdd'], \
320 | cfg['pa_ilimit'], cfg['tx_vdd_delay'], cfg['flags'],\
321 | cfg['callsign'] = struct.unpack("!BIBHHHHHH8s", pay)
322 |
323 | cfg['callsign'] = cfg['callsign'].decode("utf-8")
324 |
325 | return cfg
326 | else:
327 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
328 |
329 | def set_cfg(self, cfg_in):
330 |
331 | cfg = dict(cfg_in)
332 |
333 | cfg['callsign'] = cfg['callsign'].encode("utf-8")
334 |
335 | data = struct.pack("!BIBHHHHHH8s", \
336 | cfg['cfg_ver'], cfg['freq'], cfg['modem_config'], \
337 | cfg['txco_vpull'], cfg['tx_gate_bias'], cfg['tx_vdd'], \
338 | cfg['pa_ilimit'], cfg['tx_vdd_delay'], cfg['flags'], \
339 | cfg['callsign'])
340 |
341 | self.send_pkt(Command.SET_CFG, data)
342 |
343 | (cmd, pay) = self.recv()
344 |
345 | if cmd == Command.ERROR.value | Command.REPLY.value:
346 | err = RadioException(pay[0])
347 | raise err
348 |
349 | elif cmd == Command.SET_CFG.value | Command.REPLY.value:
350 | return
351 | else:
352 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
353 |
354 |
355 | def save_cfg(self):
356 | self.send_pkt(Command.SAVE_CFG)
357 |
358 | (cmd, pay) = self.recv()
359 |
360 | if cmd == Command.ERROR.value | Command.REPLY.value:
361 | err = RadioException(pay[0])
362 | raise err
363 |
364 | elif cmd == Command.SAVE_CFG.value | Command.REPLY.value:
365 | return
366 | else:
367 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
368 |
369 | def cfg_default(self):
370 | self.send_pkt(Command.CFG_DEFAULT)
371 |
372 | (cmd, pay) = self.recv()
373 |
374 | if cmd == Command.ERROR.value | Command.REPLY.value:
375 | err = RadioException(pay[0])
376 | raise err
377 |
378 | elif cmd == Command.CFG_DEFAULT.value | Command.REPLY.value:
379 | return
380 | else:
381 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
382 |
383 | def get_temps(self):
384 | self.send_pkt(Command.GET_TEMPS)
385 |
386 | (cmd, pay) = self.recv()
387 |
388 | if cmd == (Command.ERROR.value | Command.REPLY.value):
389 | err = RadioException(pay[0])
390 | raise err
391 |
392 | elif cmd == (Command.GET_TEMPS.value | Command.REPLY.value):
393 | temps = {}
394 | temps['pa_temp'], temps['mcu_temp'] = struct.unpack("!hh", pay)
395 | return temps
396 |
397 | else:
398 | raise Exception('Unexpected response: ' + str((hex(cmd), pay)))
399 |
400 |
401 |
402 | def close(self):
403 | self.ser.close()
404 |
405 | def usage():
406 | print('Usage: python3', sys.argv[0], '/dev/ [rx | tx n | get-cfg | load-cfg | save-cfg | default-cfg | tx-psr | tx-abort | reset | nop | get_temp]')
407 |
408 | if __name__ == '__main__':
409 |
410 | if len(sys.argv) < 2:
411 | usage()
412 | exit(1)
413 |
414 | radio = Radio(sys.argv[1])
415 |
416 | if (len(sys.argv) == 2 or sys.argv[2] == 'rx'):
417 | numPackets = 0
418 | while True:
419 | pkt = radio.rx()
420 | print('RX>', pkt)
421 | numPackets += 1
422 | print('Received ' + str(numPackets) + ' packet(s)')
423 |
424 | elif (sys.argv[2] == 'tx' and len(sys.argv) == 4):
425 | sleep(1)
426 | for i in range(int(sys.argv[3])):
427 | data = ('KC2QOL ' + str(i + 1) + ' ')
428 | data = data + ''.join([chr(randint(0x20, 0x7f)) for _ in range(0, 255 - len(data))])
429 | # data = ('KC2QOL ' + str(i + 1) + ' ').ljust(255, 'x')
430 | print('TX>', data)
431 | radio.tx(data)
432 | print('Sent ' + str(len(data)) + ' byte(s)')
433 | # Look ma, no sleep!
434 |
435 | elif (sys.argv[2] == 'get-cfg' and len(sys.argv) == 3):
436 | print(json.dumps(radio.get_cfg(), indent=4))
437 |
438 | elif (sys.argv[2] == 'load-cfg' and len(sys.argv) == 4):
439 | with open(sys.argv[3], 'r') as f:
440 | cfg = json.load(f)
441 |
442 | radio.set_cfg(cfg)
443 | new_cfg = json.dumps(radio.get_cfg(), indent=4)
444 | print("Successfully loaded config:", sys.argv[3])
445 | print(new_cfg)
446 |
447 | elif (sys.argv[2] == 'save-cfg' and len(sys.argv) == 3):
448 | radio.save_cfg()
449 |
450 | elif (sys.argv[2] == 'default-cfg' and len(sys.argv) == 3):
451 | radio.cfg_default()
452 |
453 | elif (sys.argv[2] == 'tx-psr' and len(sys.argv) == 3):
454 | radio.tx_psr()
455 |
456 | elif (sys.argv[2] == 'tx-abort' and len(sys.argv) == 3):
457 | radio.tx_abort()
458 |
459 | elif (sys.argv[2] == 'reset' and len(sys.argv) == 3):
460 | radio.nop_flush(60)
461 | radio.flush_serial()
462 | sleep(0.1)
463 | radio.flush_serial()
464 | radio.reset()
465 |
466 | elif (sys.argv[2] == 'nop' and len(sys.argv) == 3):
467 | radio.nop()
468 |
469 | elif (sys.argv[2] == 'get_temp' and len(sys.argv) == 3):
470 | temps = radio.get_temps()
471 | print("MCU temp: " + str(temps['mcu_temp']*0.01) + " PA temp: " + str(temps['pa_temp']*0.01))
472 |
473 | else:
474 | print('Usage: python3', sys.argv[0], '/dev/ [rx | tx n | get-cfg | load-cfg | save-cfg | default-cfg | tx-psr | tx-abort | reset]')
475 |
--------------------------------------------------------------------------------
/modem_configs.h:
--------------------------------------------------------------------------------
1 | /*
2 | * modem_configs.h
3 | *
4 | * Created on: Jan 23, 2019
5 | * Author: iracigt
6 | */
7 |
8 | #ifndef MODEM_CONFIGS_H_
9 | #define MODEM_CONFIGS_H_
10 |
11 | #include
12 |
13 | struct modem_cfg {
14 | uint8_t modem_mod_type_12[16];
15 | uint8_t modem_freq_dev_0_1[5];
16 | uint8_t modem_tx_ramp_delay_8[12];
17 | uint8_t modem_bcr_osr_1_9[13];
18 | uint8_t modem_afc_gear[11];
19 | uint8_t modem_agc_window_size_9[13];
20 | uint8_t modem_ook_cnt1_9[13];
21 | uint8_t modem_chflt_coe13_7_0_12[16];
22 | uint8_t modem_chflt_coe1_7_0_12[16];
23 | uint8_t modem_chflt_coe7_7_0_12[16];
24 | uint8_t rf_synth_pfdcp_cpff[11];
25 | };
26 |
27 | #define CFG_DATA_5K_DEV_1K25 { \
28 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x03, 0x0D, 0x40, 0x05, 0x8C, 0xBA, 0x80, 0x00, 0x00 }, \
29 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0x65 }, \
30 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0xB0, 0x20 }, \
31 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x51, 0x06, 0x4D, 0x32, 0x06, 0x52, 0x02, 0x00 }, \
32 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x12, 0x80, 0x32, 0x01, 0x6D, 0xA0 }, \
33 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x12, 0x12, 0x00, 0x1A, 0x40, 0x00, 0x00, 0x28 }, \
34 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x00, 0xEC, 0x01, 0x80, 0xFF }, \
35 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0x7E, 0x64, 0x1B, 0xBA, 0x58, 0x0B, 0xDD, 0xCE, 0xD6, 0xE6, 0xF6, 0x00 }, \
36 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0x03, 0x03, 0x15, 0xF0, 0x3F, 0x00, 0x7E, 0x64, 0x1B, 0xBA, 0x58, 0x0B }, \
37 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xDD, 0xCE, 0xD6, 0xE6, 0xF6, 0x00, 0x03, 0x03, 0x15, 0xF0, 0x3F, 0x00 }, \
38 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 }, \
39 | }
40 |
41 | #define CFG_DATA_5K_DEV_2K5 { \
42 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x03, 0x0D, 0x40, 0x05, 0x8C, 0xBA, 0x80, 0x00, 0x00 }, \
43 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0xCA }, \
44 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0xB0, 0x20 }, \
45 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x51, 0x06, 0x4D, 0x32, 0x06, 0x52, 0x02, 0x00 }, \
46 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x12, 0x80, 0x32, 0x01, 0xC7, 0xA0 }, \
47 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x12, 0x12, 0x00, 0x1A, 0x40, 0x00, 0x00, 0x28 }, \
48 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x00, 0xEC, 0x01, 0x80, 0xFF }, \
49 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11 }, \
50 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1 }, \
51 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00 }, \
52 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 }, \
53 | }
54 |
55 |
56 | #define CFG_DATA_10K_DEV_5K { \
57 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x06, 0x1A, 0x80, 0x05, 0x8C, 0xBA, 0x80, 0x00, 0x01 }, \
58 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0x93 }, \
59 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0x70, 0x20 }, \
60 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x51, 0x06, 0x4D, 0x32, 0x06, 0x52, 0x02, 0x0 }, \
61 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x12, 0x80, 0x65, 0x01, 0xC2, 0xA0 }, \
62 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x12, 0x12, 0x00, 0x1A, 0x40, 0x00, 0x00, 0x28 }, \
63 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x00, 0xEC, 0x01, 0x80, 0xFF }, \
64 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11 }, \
65 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1 }, \
66 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00 }, \
67 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 }, \
68 | }
69 |
70 | #define CFG_DATA_10K_DEV_2K5 { \
71 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x06, 0x1A, 0x80, 0x05, 0x8C, 0xBA, 0x80, 0x00, 0x00 }, \
72 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0xCA }, \
73 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0x70, 0x20 }, \
74 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x51, 0x06, 0x4D, 0x32, 0x07, 0xFF, 0x02, 0x00 }, \
75 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x12, 0x80, 0x65, 0x01, 0x69, 0xA0 }, \
76 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x12, 0x12, 0x00, 0x1A, 0x20, 0x00, 0x00, 0x28 }, \
77 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x00, 0x76, 0x01, 0x80, 0xFF }, \
78 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0x7E, 0x64, 0x1B, 0xBA, 0x58, 0x0B, 0xDD, 0xCE, 0xD6, 0xE6, 0xF6, 0x00 }, \
79 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0x03, 0x03, 0x15, 0xF0, 0x3F, 0x00, 0x7E, 0x64, 0x1B, 0xBA, 0x58, 0x0B }, \
80 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xDD, 0xCE, 0xD6, 0xE6, 0xF6, 0x00, 0x03, 0x03, 0x15, 0xF0, 0x3F, 0x00 }, \
81 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 }, \
82 | }
83 |
84 | #define CFG_DATA_25K_DEV_6K25 { \
85 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x07, 0xA1, 0x20, 0x09, 0x8C, 0xBA, 0x80, 0x00, 0x01 }, \
86 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0xF8 }, \
87 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0x30, 0x20 }, \
88 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x41, 0x07, 0xE0, 0x7E, 0x07, 0xFF, 0x02, 0x00 }, \
89 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x12, 0x80, 0xFC, 0x01, 0x68, 0xA0 }, \
90 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x0E, 0x0E, 0x00, 0x1A, 0x20, 0x00, 0x00, 0x28 }, \
91 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x00, 0x93, 0x01, 0x80, 0xFF }, \
92 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11 }, \
93 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1 }, \
94 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00 }, \
95 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 }, \
96 | }
97 |
98 | #define CFG_DATA_25K_DEV_12K5 { \
99 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x07, 0xA1, 0x20, 0x09, 0x8C, 0xBA, 0x80, 0x00, 0x03 }, \
100 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0xF0 }, \
101 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0x30, 0x20 }, \
102 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x41, 0x07, 0xE0, 0x7E, 0x07, 0xE0, 0x02, 0x00 }, \
103 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x12, 0x80, 0xFC, 0x01, 0xC0, 0xA0 }, \
104 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x0E, 0x0E, 0x00, 0x1A, 0x40, 0x00, 0x00, 0x28 }, \
105 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x01, 0x27, 0x01, 0x80, 0xFF }, \
106 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01 }, \
107 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F, 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9 }, \
108 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01, 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F }, \
109 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 }, \
110 | }
111 |
112 | #define CFG_DATA_50K_DEV_12K5 { \
113 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x0F, 0x42, 0x40, 0x09, 0x8C, 0xBA, 0x80, 0x00, 0x03 }, \
114 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0xF0 }, \
115 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0x20, 0x20 }, \
116 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x41, 0x07, 0xE0, 0x7E, 0x07, 0xFF, 0x02, 0x00 }, \
117 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x12, 0x81, 0xF8, 0x01, 0x67, 0xA0 }, \
118 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x0E, 0x0E, 0x00, 0x1A, 0x20, 0x00, 0x00, 0x28 }, \
119 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x00, 0x93, 0x01, 0x80, 0xFF }, \
120 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11 }, \
121 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1 }, \
122 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00 }, \
123 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 }, \
124 | }
125 |
126 | #define CFG_DATA_50K_DEV_25K { \
127 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x0F, 0x42, 0x40, 0x09, 0x8C, 0xBA, 0x80, 0x00, 0x07 }, \
128 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0xE0 }, \
129 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0x20, 0x20 }, \
130 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x41, 0x07, 0xE0, 0x7E, 0x07, 0xE0, 0x02, 0x00 }, \
131 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x12, 0x81, 0xF8, 0x01, 0xBE, 0xA0 }, \
132 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x0E, 0x0E, 0x00, 0x1A, 0x40, 0x00, 0x00, 0x28 }, \
133 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x01, 0x27, 0x01, 0x80, 0xFF }, \
134 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01 }, \
135 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F, 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9 }, \
136 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01, 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F }, \
137 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 }, \
138 | }
139 |
140 | #define CFG_DATA_100K_DEV_25K { \
141 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x1E, 0x84, 0x80, 0x09, 0x8C, 0xBA, 0x80, 0x00, 0x07 }, \
142 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0xE0 }, \
143 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0x10, 0x20 }, \
144 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x41, 0x07, 0xE0, 0x7E, 0x07, 0xFF, 0x02, 0x00 }, \
145 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x23, 0x87, 0xE0, 0x00, 0xA4, 0xA0 }, \
146 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x0E, 0x0E, 0x00, 0x1A, 0x20, 0x00, 0x00, 0x28 }, \
147 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x00, 0x93, 0x01, 0x80, 0xFF }, \
148 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11 }, \
149 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1 }, \
150 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00 }, \
151 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x34, 0x04, 0x0B, 0x04, 0x07, 0x70, 0x03 }, \
152 | }
153 |
154 | #define CFG_DATA_100K_DEV_50K { \
155 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x1E, 0x84, 0x80, 0x09, 0x8C, 0xBA, 0x80, 0x00, 0x0F }, \
156 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0xC1 }, \
157 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0x10, 0x20 }, \
158 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x41, 0x07, 0xE0, 0x7E, 0x07, 0xE0, 0x02, 0x00 }, \
159 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x23, 0x87, 0xE0, 0x00, 0xCD, 0xA0 }, \
160 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x0E, 0x0E, 0x00, 0x1A, 0x40, 0x00, 0x00, 0x28 }, \
161 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x01, 0x27, 0x01, 0x80, 0xFF }, \
162 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01 }, \
163 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F, 0xFF, 0xBA, 0x0F, 0x51, 0xCF, 0xA9 }, \
164 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xC9, 0xFC, 0x1B, 0x1E, 0x0F, 0x01, 0xFC, 0xFD, 0x15, 0xFF, 0x00, 0x0F }, \
165 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x34, 0x04, 0x0B, 0x04, 0x07, 0x70, 0x03 }, \
166 | }
167 |
168 | // TEMPLATE
169 | //#define FILL_IN { \
170 | // .modem_mod_type_12 = { FILL_IN }, \
171 | // .modem_freq_dev_0_1 = { FILL_IN }, \
172 | // .modem_tx_ramp_delay_8 = { FILL_IN }, \
173 | // .modem_bcr_osr_1_9 = { FILL_IN }, \
174 | // .modem_afc_gear = { FILL_IN }, \
175 | // .modem_agc_window_size_9 = { FILL_IN }, \
176 | // .modem_ook_cnt1_9 = { FILL_IN }, \
177 | // .modem_chflt_coe13_7_0_12 = { FILL_IN }, \
178 | // .modem_chflt_coe1_7_0_12 = { FILL_IN }, \
179 | // .modem_chflt_coe7_7_0_12 = { FILL_IN }, \
180 | // .rf_synth_pfdcp_cpff = { FILL_IN }, \
181 | //}
182 |
183 |
184 | // UNUSED
185 | #define CFG_40K_DATA_20K_DEV { \
186 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x06, 0x1A, 0x80, 0x05, 0x8C, 0xBA, 0x80, 0x00, 0x06 }, \
187 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0x4D }, \
188 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0x20, 0x20 }, \
189 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x51, 0x06, 0x4D, 0x32, 0x06, 0x52, 0x02, 0x00 }, \
190 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x12, 0x81, 0x93, 0x01, 0xC0, 0xA0 }, \
191 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x12, 0x12, 0x00, 0x1A, 0x40, 0x00, 0x00, 0x28 }, \
192 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x00, 0xEC, 0x01, 0x80, 0xFF }, \
193 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11 }, \
194 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00, 0xCC, 0xA1, 0x30, 0xA0, 0x21, 0xD1 }, \
195 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xB9, 0xC9, 0xEA, 0x05, 0x12, 0x11, 0x0A, 0x04, 0x15, 0xFC, 0x03, 0x00 }, \
196 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 }, \
197 | }
198 |
199 | #define CFG_40K_DATA_10K_DEV { \
200 | .modem_mod_type_12 = { 0x11, 0x20, 0x0C, 0x00, 0x03, 0x00, 0x07, 0x0C, 0x35, 0x00, 0x09, 0x8C, 0xBA, 0x80, 0x00, 0x03 }, \
201 | .modem_freq_dev_0_1 = { 0x11, 0x20, 0x01, 0x0C, 0x27 }, \
202 | .modem_tx_ramp_delay_8 = { 0x11, 0x20, 0x08, 0x18, 0x01, 0x00, 0x08, 0x03, 0x80, 0x00, 0x20, 0x20 }, \
203 | .modem_bcr_osr_1_9 = { 0x11, 0x20, 0x09, 0x22, 0x00, 0x51, 0x06, 0x4D, 0x32, 0x07, 0xFF, 0x02, 0x00 }, \
204 | .modem_afc_gear = { 0x11, 0x20, 0x07, 0x2C, 0x00, 0x12, 0x81, 0x93, 0x01, 0x90, 0xA0 }, \
205 | .modem_agc_window_size_9 = { 0x11, 0x20, 0x09, 0x38, 0x11, 0x12, 0x12, 0x00, 0x1A, 0x20, 0x00, 0x00, 0x28 }, \
206 | .modem_ook_cnt1_9 = { 0x11, 0x20, 0x09, 0x42, 0xA4, 0x03, 0xD6, 0x03, 0x00, 0x76, 0x01, 0x80, 0xFF }, \
207 | .modem_chflt_coe13_7_0_12 = { 0x11, 0x21, 0x0C, 0x00, 0xA2, 0x81, 0x26, 0xAF, 0x3F, 0xEE, 0xC8, 0xC7, 0xDB, 0xF2, 0x02, 0x08 }, \
208 | .modem_chflt_coe1_7_0_12 = { 0x11, 0x21, 0x0C, 0x0C, 0x07, 0x03, 0x15, 0xFC, 0x0F, 0x00, 0xA2, 0x81, 0x26, 0xAF, 0x3F, 0xEE }, \
209 | .modem_chflt_coe7_7_0_12 = { 0x11, 0x21, 0x0C, 0x18, 0xC8, 0xC7, 0xDB, 0xF2, 0x02, 0x08, 0x07, 0x03, 0x15, 0xFC, 0x0F, 0x00 }, \
210 | .rf_synth_pfdcp_cpff = { 0x11, 0x23, 0x07, 0x00, 0x2C, 0x0E, 0x0B, 0x04, 0x0C, 0x73, 0x03 }, \
211 | }
212 |
213 |
214 |
215 | #endif /* MODEM_CONFIGS_H_ */
216 |
--------------------------------------------------------------------------------
/lnk_msp430fr5994.cmd:
--------------------------------------------------------------------------------
1 | /* Little Free Radio - An Open Source Radio for CubeSats
2 | * A project of the University at Buffalo Nanosatellite Laboratory
3 | *
4 | * This file is provided under the following licensing conditions:
5 | *
6 | *
7 | * Copyright (C) 2012 - 2017 Texas Instruments Incorporated - http://www.ti.com/
8 | *
9 | * Redistribution and use in source and binary forms, with or without
10 | * modification, are permitted provided that the following conditions
11 | * are met:
12 | *
13 | * Redistributions of source code must retain the above copyright
14 | * notice, this list of conditions and the following disclaimer.
15 | *
16 | * Redistributions in binary form must reproduce the above copyright
17 | * notice, this list of conditions and the following disclaimer in the
18 | * documentation and/or other materials provided with the
19 | * distribution.
20 | *
21 | * Neither the name of Texas Instruments Incorporated nor the names of
22 | * its contributors may be used to endorse or promote products derived
23 | * from this software without specific prior written permission.
24 | *
25 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 | *
37 | * Default linker command file for Texas Instruments MSP430FR5994
38 | *
39 | *****************************************************************************/
40 |
41 | /******************************************************************************/
42 | /* */
43 | /* Usage: lnk430 -o -m