├── .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 lnk.cmd */ 44 | /* cl430 -z -o -m lnk.cmd */ 45 | /* */ 46 | /*----------------------------------------------------------------------------*/ 47 | /* These linker options are for command line linking only. For IDE linking, */ 48 | /* you should set your linker options in Project Properties */ 49 | /* -c LINK USING C CONVENTIONS */ 50 | /* -stack 0x0100 SOFTWARE STACK SIZE */ 51 | /* -heap 0x0100 HEAP AREA SIZE */ 52 | /* */ 53 | /*----------------------------------------------------------------------------*/ 54 | /* 1.203 */ 55 | /*----------------------------------------------------------------------------*/ 56 | 57 | /****************************************************************************/ 58 | /* SPECIFY THE SYSTEM MEMORY MAP */ 59 | /****************************************************************************/ 60 | 61 | MEMORY 62 | { 63 | TINYRAM : origin = 0xA, length = 0x16 64 | BSL : origin = 0x1000, length = 0x800 65 | INFOD : origin = 0x1800, length = 0x80 66 | INFOC : origin = 0x1880, length = 0x80 67 | INFOB : origin = 0x1900, length = 0x80 68 | INFOA : origin = 0x1980, length = 0x80 69 | RAM : origin = 0x1C00, length = 0x1000 70 | FRAM : origin = 0x4000, length = 0xBF80 71 | FRAM2 : origin = 0x10000,length = 0x34000 72 | JTAGSIGNATURE : origin = 0xFF80, length = 0x0004, fill = 0xFFFF 73 | BSLSIGNATURE : origin = 0xFF84, length = 0x0004, fill = 0xFFFF 74 | IPESIGNATURE : origin = 0xFF88, length = 0x0008, fill = 0xFFFF 75 | INT00 : origin = 0xFF90, length = 0x0002 76 | INT01 : origin = 0xFF92, length = 0x0002 77 | INT02 : origin = 0xFF94, length = 0x0002 78 | INT03 : origin = 0xFF96, length = 0x0002 79 | INT04 : origin = 0xFF98, length = 0x0002 80 | INT05 : origin = 0xFF9A, length = 0x0002 81 | INT06 : origin = 0xFF9C, length = 0x0002 82 | INT07 : origin = 0xFF9E, length = 0x0002 83 | INT08 : origin = 0xFFA0, length = 0x0002 84 | INT09 : origin = 0xFFA2, length = 0x0002 85 | INT10 : origin = 0xFFA4, length = 0x0002 86 | INT11 : origin = 0xFFA6, length = 0x0002 87 | INT12 : origin = 0xFFA8, length = 0x0002 88 | INT13 : origin = 0xFFAA, length = 0x0002 89 | INT14 : origin = 0xFFAC, length = 0x0002 90 | INT15 : origin = 0xFFAE, length = 0x0002 91 | INT16 : origin = 0xFFB0, length = 0x0002 92 | INT17 : origin = 0xFFB2, length = 0x0002 93 | INT18 : origin = 0xFFB4, length = 0x0002 94 | INT19 : origin = 0xFFB6, length = 0x0002 95 | INT20 : origin = 0xFFB8, length = 0x0002 96 | INT21 : origin = 0xFFBA, length = 0x0002 97 | INT22 : origin = 0xFFBC, length = 0x0002 98 | INT23 : origin = 0xFFBE, length = 0x0002 99 | INT24 : origin = 0xFFC0, length = 0x0002 100 | INT25 : origin = 0xFFC2, length = 0x0002 101 | INT26 : origin = 0xFFC4, length = 0x0002 102 | INT27 : origin = 0xFFC6, length = 0x0002 103 | INT28 : origin = 0xFFC8, length = 0x0002 104 | INT29 : origin = 0xFFCA, length = 0x0002 105 | INT30 : origin = 0xFFCC, length = 0x0002 106 | INT31 : origin = 0xFFCE, length = 0x0002 107 | INT32 : origin = 0xFFD0, length = 0x0002 108 | INT33 : origin = 0xFFD2, length = 0x0002 109 | INT34 : origin = 0xFFD4, length = 0x0002 110 | INT35 : origin = 0xFFD6, length = 0x0002 111 | INT36 : origin = 0xFFD8, length = 0x0002 112 | INT37 : origin = 0xFFDA, length = 0x0002 113 | INT38 : origin = 0xFFDC, length = 0x0002 114 | INT39 : origin = 0xFFDE, length = 0x0002 115 | INT40 : origin = 0xFFE0, length = 0x0002 116 | INT41 : origin = 0xFFE2, length = 0x0002 117 | INT42 : origin = 0xFFE4, length = 0x0002 118 | INT43 : origin = 0xFFE6, length = 0x0002 119 | INT44 : origin = 0xFFE8, length = 0x0002 120 | INT45 : origin = 0xFFEA, length = 0x0002 121 | INT46 : origin = 0xFFEC, length = 0x0002 122 | INT47 : origin = 0xFFEE, length = 0x0002 123 | INT48 : origin = 0xFFF0, length = 0x0002 124 | INT49 : origin = 0xFFF2, length = 0x0002 125 | INT50 : origin = 0xFFF4, length = 0x0002 126 | INT51 : origin = 0xFFF6, length = 0x0002 127 | INT52 : origin = 0xFFF8, length = 0x0002 128 | INT53 : origin = 0xFFFA, length = 0x0002 129 | INT54 : origin = 0xFFFC, length = 0x0002 130 | RESET : origin = 0xFFFE, length = 0x0002 131 | } 132 | 133 | /****************************************************************************/ 134 | /* Specify the LEA memory map */ 135 | /****************************************************************************/ 136 | 137 | #define LEASTACK_SIZE 0x138 138 | 139 | MEMORY 140 | { 141 | LEARAM : origin = 0x2C00, length = 0x1000 - LEASTACK_SIZE 142 | LEASTACK : origin = 0x3C00 - LEASTACK_SIZE, length = LEASTACK_SIZE 143 | } 144 | 145 | /****************************************************************************/ 146 | /* SPECIFY THE SECTIONS ALLOCATION INTO MEMORY */ 147 | /****************************************************************************/ 148 | 149 | SECTIONS 150 | { 151 | GROUP(RW_IPE) 152 | { 153 | 154 | GROUP(READ_WRITE_MEMORY) 155 | { 156 | 157 | .TI.persistent : {} /* For #pragma persistent */ 158 | .cio : {} /* C I/O Buffer */ 159 | .sysmem : {} /* Dynamic memory allocation area */ 160 | } PALIGN(0x0400), RUN_START(fram_rw_start) 161 | 162 | GROUP(IPENCAPSULATED_MEMORY) 163 | { 164 | 165 | .ipestruct : {} /* IPE Data structure */ 166 | .ipe : {} /* IPE */ 167 | .ipe_const : {} /* IPE Protected constants */ 168 | .ipe:_isr : {} /* IPE ISRs */ 169 | } PALIGN(0x0400), RUN_START(fram_ipe_start) RUN_END(fram_ipe_end) RUN_END(fram_rx_start) 170 | 171 | } > 0x4000 172 | 173 | .cinit : {} > FRAM /* Initialization tables */ 174 | .binit : {} > FRAM /* Boot-time Initialization tables */ 175 | .pinit : {} > FRAM /* C++ Constructor tables */ 176 | .init_array : {} > FRAM /* C++ Constructor tables */ 177 | .mspabi.exidx : {} > FRAM /* C++ Constructor tables */ 178 | .mspabi.extab : {} > FRAM /* C++ Constructor tables */ 179 | .text:_isr : {} > FRAM /* Code ISRs */ 180 | 181 | #ifndef __LARGE_DATA_MODEL__ 182 | .const : {} > FRAM /* Constant data */ 183 | #else 184 | .const : {} >> FRAM | FRAM2 /* Constant data */ 185 | #endif 186 | 187 | #ifndef __LARGE_CODE_MODEL__ 188 | .text : {} > FRAM /* Code */ 189 | #else 190 | .text : {} >> FRAM2 | FRAM /* Code */ 191 | #endif 192 | 193 | #ifdef __TI_COMPILER_VERSION__ 194 | #if __TI_COMPILER_VERSION__ >= 15009000 195 | #ifndef __LARGE_CODE_MODEL__ 196 | .TI.ramfunc : {} load=FRAM, run=RAM, table(BINIT) 197 | #else 198 | .TI.ramfunc : {} load=FRAM | FRAM2, run=RAM, table(BINIT) 199 | #endif 200 | #endif 201 | #endif 202 | 203 | .jtagsignature : {} > JTAGSIGNATURE 204 | .bslsignature : {} > BSLSIGNATURE 205 | 206 | GROUP(SIGNATURE_SHAREDMEMORY) 207 | { 208 | .ipesignature : {} /* IPE Signature */ 209 | .jtagpassword : {} /* JTAG Password */ 210 | } > IPESIGNATURE 211 | 212 | .bss : {} > RAM /* Global & static vars */ 213 | .data : {} > RAM /* Global & static vars */ 214 | .TI.noinit : {} > RAM /* For #pragma noinit */ 215 | .stack : {} > RAM (HIGH) /* Software system stack */ 216 | 217 | .tinyram : {} > TINYRAM /* Tiny RAM */ 218 | 219 | /* MSP430 INFO memory segments */ 220 | .infoA : type = NOINIT{} > INFOA 221 | .infoB : type = NOINIT{} > INFOB 222 | .infoC : type = NOINIT{} > INFOC 223 | .infoD : type = NOINIT{} > INFOD 224 | 225 | 226 | .leaRAM : {} > LEARAM /* LEA RAM */ 227 | .leaStack : {} > LEASTACK (HIGH) /* LEA STACK */ 228 | 229 | /* MSP430 interrupt vectors */ 230 | 231 | .int00 : {} > INT00 232 | .int01 : {} > INT01 233 | .int02 : {} > INT02 234 | .int03 : {} > INT03 235 | .int04 : {} > INT04 236 | .int05 : {} > INT05 237 | .int06 : {} > INT06 238 | .int07 : {} > INT07 239 | .int08 : {} > INT08 240 | .int09 : {} > INT09 241 | .int10 : {} > INT10 242 | .int11 : {} > INT11 243 | .int12 : {} > INT12 244 | .int13 : {} > INT13 245 | .int14 : {} > INT14 246 | .int15 : {} > INT15 247 | .int16 : {} > INT16 248 | .int17 : {} > INT17 249 | LEA : { * ( .int18 ) } > INT18 type = VECT_INIT 250 | PORT8 : { * ( .int19 ) } > INT19 type = VECT_INIT 251 | PORT7 : { * ( .int20 ) } > INT20 type = VECT_INIT 252 | EUSCI_B3 : { * ( .int21 ) } > INT21 type = VECT_INIT 253 | EUSCI_B2 : { * ( .int22 ) } > INT22 type = VECT_INIT 254 | EUSCI_B1 : { * ( .int23 ) } > INT23 type = VECT_INIT 255 | EUSCI_A3 : { * ( .int24 ) } > INT24 type = VECT_INIT 256 | EUSCI_A2 : { * ( .int25 ) } > INT25 type = VECT_INIT 257 | PORT6 : { * ( .int26 ) } > INT26 type = VECT_INIT 258 | PORT5 : { * ( .int27 ) } > INT27 type = VECT_INIT 259 | TIMER4_A1 : { * ( .int28 ) } > INT28 type = VECT_INIT 260 | TIMER4_A0 : { * ( .int29 ) } > INT29 type = VECT_INIT 261 | AES256 : { * ( .int30 ) } > INT30 type = VECT_INIT 262 | RTC_C : { * ( .int31 ) } > INT31 type = VECT_INIT 263 | PORT4 : { * ( .int32 ) } > INT32 type = VECT_INIT 264 | PORT3 : { * ( .int33 ) } > INT33 type = VECT_INIT 265 | TIMER3_A1 : { * ( .int34 ) } > INT34 type = VECT_INIT 266 | TIMER3_A0 : { * ( .int35 ) } > INT35 type = VECT_INIT 267 | PORT2 : { * ( .int36 ) } > INT36 type = VECT_INIT 268 | TIMER2_A1 : { * ( .int37 ) } > INT37 type = VECT_INIT 269 | TIMER2_A0 : { * ( .int38 ) } > INT38 type = VECT_INIT 270 | PORT1 : { * ( .int39 ) } > INT39 type = VECT_INIT 271 | TIMER1_A1 : { * ( .int40 ) } > INT40 type = VECT_INIT 272 | TIMER1_A0 : { * ( .int41 ) } > INT41 type = VECT_INIT 273 | DMA : { * ( .int42 ) } > INT42 type = VECT_INIT 274 | EUSCI_A1 : { * ( .int43 ) } > INT43 type = VECT_INIT 275 | TIMER0_A1 : { * ( .int44 ) } > INT44 type = VECT_INIT 276 | TIMER0_A0 : { * ( .int45 ) } > INT45 type = VECT_INIT 277 | ADC12_B : { * ( .int46 ) } > INT46 type = VECT_INIT 278 | EUSCI_B0 : { * ( .int47 ) } > INT47 type = VECT_INIT 279 | EUSCI_A0 : { * ( .int48 ) } > INT48 type = VECT_INIT 280 | WDT : { * ( .int49 ) } > INT49 type = VECT_INIT 281 | TIMER0_B1 : { * ( .int50 ) } > INT50 type = VECT_INIT 282 | TIMER0_B0 : { * ( .int51 ) } > INT51 type = VECT_INIT 283 | COMP_E : { * ( .int52 ) } > INT52 type = VECT_INIT 284 | UNMI : { * ( .int53 ) } > INT53 type = VECT_INIT 285 | SYSNMI : { * ( .int54 ) } > INT54 type = VECT_INIT 286 | .reset : {} > RESET /* MSP430 reset vector */ 287 | 288 | } 289 | /****************************************************************************/ 290 | /* MPU/IPE SPECIFIC MEMORY SEGMENT DEFINITONS */ 291 | /****************************************************************************/ 292 | 293 | #ifdef _IPE_ENABLE 294 | #define IPE_MPUIPLOCK 0x0080 295 | #define IPE_MPUIPENA 0x0040 296 | #define IPE_MPUIPPUC 0x0020 297 | 298 | // Evaluate settings for the control setting of IP Encapsulation 299 | #if defined(_IPE_ASSERTPUC1) 300 | #if defined(_IPE_LOCK ) && (_IPE_ASSERTPUC1 == 0x08)) 301 | fram_ipe_enable_value = (IPE_MPUIPENA | IPE_MPUIPPUC |IPE_MPUIPLOCK); 302 | #elif defined(_IPE_LOCK ) 303 | fram_ipe_enable_value = (IPE_MPUIPENA | IPE_MPUIPLOCK); 304 | #elif (_IPE_ASSERTPUC1 == 0x08) 305 | fram_ipe_enable_value = (IPE_MPUIPENA | IPE_MPUIPPUC); 306 | #else 307 | fram_ipe_enable_value = (IPE_MPUIPENA); 308 | #endif 309 | #else 310 | #if defined(_IPE_LOCK ) 311 | fram_ipe_enable_value = (IPE_MPUIPENA | IPE_MPUIPLOCK); 312 | #else 313 | fram_ipe_enable_value = (IPE_MPUIPENA); 314 | #endif 315 | #endif 316 | 317 | // Segment definitions 318 | #ifdef _IPE_MANUAL // For custom sizes selected in the GUI 319 | fram_ipe_border1 = (_IPE_SEGB1>>4); 320 | fram_ipe_border2 = (_IPE_SEGB2>>4); 321 | #else // Automated sizes generated by the Linker 322 | fram_ipe_border2 = fram_ipe_end >> 4; 323 | fram_ipe_border1 = fram_ipe_start >> 4; 324 | #endif 325 | 326 | fram_ipe_settings_struct_address = Ipe_settingsStruct >> 4; 327 | fram_ipe_checksum = ~((fram_ipe_enable_value & fram_ipe_border2 & fram_ipe_border1) | (fram_ipe_enable_value & ~fram_ipe_border2 & ~fram_ipe_border1) | (~fram_ipe_enable_value & fram_ipe_border2 & ~fram_ipe_border1) | (~fram_ipe_enable_value & ~fram_ipe_border2 & fram_ipe_border1)); 328 | #endif 329 | 330 | #ifdef _MPU_ENABLE 331 | #define MPUPW (0xA500) /* MPU Access Password */ 332 | #define MPUENA (0x0001) /* MPU Enable */ 333 | #define MPULOCK (0x0002) /* MPU Lock */ 334 | #define MPUSEGIE (0x0010) /* MPU Enable NMI on Segment violation */ 335 | 336 | __mpu_enable = 1; 337 | // Segment definitions 338 | #ifdef _MPU_MANUAL // For custom sizes selected in the GUI 339 | mpu_segment_border1 = _MPU_SEGB1 >> 4; 340 | mpu_segment_border2 = _MPU_SEGB2 >> 4; 341 | mpu_sam_value = (_MPU_SAM0 << 12) | (_MPU_SAM3 << 8) | (_MPU_SAM2 << 4) | _MPU_SAM1; 342 | #else // Automated sizes generated by Linker 343 | #ifdef _IPE_ENABLE //if IPE is used in project too 344 | //seg1 = any read + write persistent variables 345 | //seg2 = ipe = read + write + execute access 346 | //seg3 = code, read + execute only 347 | mpu_segment_border1 = fram_ipe_start >> 4; 348 | mpu_segment_border2 = fram_rx_start >> 4; 349 | mpu_sam_value = 0x1573; // Info R, Seg3 RX, Seg2 RWX, Seg1 RW 350 | #else 351 | mpu_segment_border1 = fram_rx_start >> 4; 352 | mpu_segment_border2 = fram_rx_start >> 4; 353 | mpu_sam_value = 0x1513; // Info R, Seg3 RX, Seg2 R, Seg1 RW 354 | #endif 355 | #endif 356 | #ifdef _MPU_LOCK 357 | #ifdef _MPU_ENABLE_NMI 358 | mpu_ctl0_value = MPUPW | MPUENA | MPULOCK | MPUSEGIE; 359 | #else 360 | mpu_ctl0_value = MPUPW | MPUENA | MPULOCK; 361 | #endif 362 | #else 363 | #ifdef _MPU_ENABLE_NMI 364 | mpu_ctl0_value = MPUPW | MPUENA | MPUSEGIE; 365 | #else 366 | mpu_ctl0_value = MPUPW | MPUENA; 367 | #endif 368 | #endif 369 | #endif 370 | 371 | /****************************************************************************/ 372 | /* INCLUDE PERIPHERALS MEMORY MAP */ 373 | /****************************************************************************/ 374 | 375 | -l msp430fr5994.cmd 376 | -------------------------------------------------------------------------------- /mcu.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 23 | #include "mcu.h" 24 | #include "error.h" 25 | #include "adc.h" 26 | 27 | 28 | #define CYC_PER_US 16 29 | #define FOR_LOOP_CYC 4 // guessing here... 30 | #define F_CPU 8000000L 31 | //#define F_SMCLK (4000000ul) 32 | #define F_SMCLK (8000000ul) 33 | 34 | 35 | #define SPI_CLKDIV (F_SMCLK / 8000000L) //8 MHz SPI 36 | #define FREQ_I2C (400000ul) 37 | 38 | volatile unsigned char _i2c_tx_byte_ctr; //bytes remaining this transaction 39 | unsigned char _i2c_tx_bytes; //bytes to send this transaction 40 | unsigned char *_i2c_tx_data; //pointer to buffer of data to send 41 | volatile bool _i2c_did_nack = false; 42 | volatile bool _i2c_tx_done = false; 43 | 44 | static volatile unsigned char *const gpio_dir_for_port[] = 45 | { 46 | 0x0000, 47 | &P1DIR, 48 | &P2DIR, 49 | &P3DIR, 50 | &P4DIR, 51 | &P5DIR, 52 | &P6DIR, 53 | &P7DIR, 54 | &P8DIR, 55 | }; 56 | 57 | static volatile unsigned char *const gpio_out_for_port[] = 58 | { 59 | 0x0000, 60 | &P1OUT, 61 | &P2OUT, 62 | &P3OUT, 63 | &P4OUT, 64 | &P5OUT, 65 | &P6OUT, 66 | &P7OUT, 67 | &P8OUT, 68 | }; 69 | 70 | static volatile unsigned char *const gpio_in_for_port[] = 71 | { 72 | 0x0000, 73 | &P1IN, 74 | &P2IN, 75 | &P3IN, 76 | &P4IN, 77 | &P5IN, 78 | &P6IN, 79 | &P7IN, 80 | &P8IN, 81 | }; 82 | 83 | static volatile unsigned char *const gpio_ren_for_port[] = 84 | { 85 | 0x0000, 86 | &P1REN, 87 | &P2REN, 88 | &P3REN, 89 | &P4REN, 90 | &P5REN, 91 | &P6REN, 92 | &P7REN, 93 | &P8REN, 94 | }; 95 | static volatile unsigned char *const gpio_ie_for_port[] = 96 | { 97 | 0x0000, 98 | &P1IE, 99 | &P2IE, 100 | &P3IE, 101 | &P4IE, 102 | &P5IE, 103 | &P6IE, 104 | &P7IE, 105 | &P8IE, 106 | }; 107 | 108 | static volatile unsigned char *const gpio_ifg_for_port[] = 109 | { 110 | 0x0000, 111 | &P1IFG, 112 | &P2IFG, 113 | &P3IFG, 114 | &P4IFG, 115 | &P5IFG, 116 | &P6IFG, 117 | &P7IFG, 118 | &P8IFG, 119 | }; 120 | 121 | static volatile unsigned char *const gpio_ies_for_port[] = 122 | { 123 | 0x0000, 124 | &P1IES, 125 | &P2IES, 126 | &P3IES, 127 | &P4IES, 128 | &P5IES, 129 | &P6IES, 130 | &P7IES, 131 | &P8IES, 132 | }; 133 | 134 | static volatile unsigned char *const gpio_sel1_for_port[] = 135 | { 136 | 0x0000, 137 | &P1SEL1, 138 | &P2SEL1, 139 | &P3SEL1, 140 | &P4SEL1, 141 | &P5SEL1, 142 | &P6SEL1, 143 | &P7SEL1, 144 | &P8SEL1, 145 | }; 146 | 147 | static volatile unsigned char *const gpio_sel0_for_port[] = 148 | { 149 | 0x0000, 150 | &P1SEL0, 151 | &P2SEL0, 152 | &P3SEL0, 153 | &P4SEL0, 154 | &P5SEL0, 155 | &P6SEL0, 156 | &P7SEL0, 157 | &P8SEL0, 158 | }; 159 | 160 | /** 161 | * @brief Initialize the backchannel/debug UART 162 | * 163 | * Configures pins, sets baud rate, and enables UCA1 as the 164 | * backchannel/debug UART for printf-style output at 9600 baud 165 | */ 166 | 167 | void bc_uart_init() 168 | { 169 | P2SEL1 |= BIT5 | BIT6; // Configure debug UART pins 170 | P2SEL0 &= ~(BIT5| BIT6); 171 | 172 | 173 | // Config UCA1 (debug UART) 174 | // For 8 MHz SMCLK 175 | UCA1CTLW0 = UCSWRST; // Put eUSCI in reset 176 | 177 | // Assuming 8 MHz SMCLK 178 | // See User's Guide Table 30-5 on page 779 179 | 180 | // UCA1CTLW0 |= UCSSEL__SMCLK; // CLK = SMCLK 181 | // // Baud Rate calculation 182 | // // Table 30-5 183 | // // 115200 184 | // UCA1BRW = 4; 185 | // // UCBRFx UCBRSx 186 | // UCA1MCTLW = UCOS16 | UCBRF_5 | 0x5500; 187 | 188 | 189 | UCA1CTLW0 |= UCSSEL__SMCLK; // CLK = SMCLK 190 | // Baud Rate calculation 191 | // Table 30-5 192 | // 460800 193 | UCA1BRW = 17; 194 | // UCBRSx 195 | UCA1MCTLW = 0x4A00; 196 | 197 | 198 | UCA1CTLW0 &= ~UCSWRST; // Initialize eUSCI 199 | } 200 | 201 | int fputc(int _c, register FILE *_fp) 202 | { 203 | while(!(UCA1IFG & UCTXIFG)); 204 | UCA1TXBUF = (unsigned char) _c; 205 | while(!(UCA1IFG & UCTXIFG)); 206 | 207 | return((unsigned char)_c); 208 | } 209 | 210 | int fputs(const char *_ptr, register FILE *_fp) 211 | { 212 | unsigned int i, len; 213 | 214 | len = strlen(_ptr); 215 | 216 | for(i=0 ; i 3 || mv > 0xFFF) { 408 | return -EINVAL; 409 | } 410 | 411 | 412 | // Multi-write (doesn't update EEPROM) 413 | // ----------------------------------------------- 414 | // Byte 1: 0 1 0 0 0 DAC1 DAC0 UDAC 415 | // Byte 2: VREF PD1 PD0 Gx D11 D10 D9 D8 416 | // Byte 3: D7 D6 D5 D4 D3 D2 D1 D0 417 | 418 | uint8_t cmd[] = { 419 | 0x40 | (chan << 1) | 0x00, // Multiwrite | channel | ~UDAC (update immediately) 420 | 0x80 | 0x00 | 0x10 | mv >> 8, // VREF = 2.048V internal | PD = Normal operation | Gain = x2 | val[11:8] 421 | mv & 0xFF // val[7:0] 422 | }; 423 | 424 | int len = i2c_write(0x60, cmd, sizeof(cmd)); 425 | return len == sizeof(cmd) ? ESUCCESS : -EINVALSTATE; 426 | } 427 | 428 | /** 429 | * @brief Delay for a specified number of microseconds. 430 | * Not calibrated yet, may be significantly wrong. 431 | */ 432 | 433 | void delay_micros(unsigned long micros) 434 | { 435 | 436 | for(; micros != 0; micros--) { 437 | __delay_cycles(CYC_PER_US - FOR_LOOP_CYC); 438 | } 439 | __delay_cycles(CYC_PER_US - FOR_LOOP_CYC); 440 | } 441 | 442 | /** 443 | * @brief Set input/output mode of a GPIO pin 444 | * 445 | * Configures a GPIO pin for input or output, with optional pull-up or 446 | * pull-down resistor for inputs. Currently does not check if the pin has 447 | * been configured for a module function, which would prevent the parameters 448 | * configured here from having any effect. 449 | * 450 | * @param pin The pin to configure. Takes a single byte, whose upper nibble 451 | * specifies the port, and lower nibble specifies the pin. E.g. 452 | * 0x23 translates to port 2, pin 3 453 | * @param mode The mode to set the pin to. Use one of the macros OUTPUT, 454 | * INPUT, INPUT_PULLUP, or INPUT_PULLDOWN 455 | */ 456 | 457 | void gpio_config(int pin, int mode) 458 | { 459 | unsigned int port = (pin >> 4) & 0x0F; 460 | unsigned int pin_num = (pin >> 0) & 0x07; 461 | unsigned char pull_enable = (mode & 0x20); 462 | unsigned char pull_direction = (mode & 0x10); 463 | unsigned char analog = (mode & 0x40); 464 | 465 | /* Set pin direction */ 466 | char cur_dir = *gpio_dir_for_port[port]; 467 | 468 | *gpio_dir_for_port[port] = (mode & OUTPUT) ? cur_dir | (1 << pin_num) : cur_dir & ~(1 << pin_num); 469 | 470 | /* Set pull resistor to up or down, if enabled */ 471 | if(pull_enable) 472 | { 473 | gpio_write(pin, pull_direction); 474 | } 475 | 476 | /* Enable or disable pull up/down resistor */ 477 | char cur_ren = *gpio_ren_for_port[port]; 478 | *gpio_ren_for_port[port] = pull_enable ? cur_ren | (1 << pin_num) : cur_ren & ~(1 << pin_num); 479 | 480 | //Configure pin mux: 00 for digital GPIO, 11 for analog (assuming pin has analog, otherwise that's some random peripheral 481 | char cur_sel0 = *gpio_sel0_for_port[port]; 482 | char cur_sel1 = *gpio_sel1_for_port[port]; 483 | //make sure we're not muxing the pin to some random peripheral 484 | if(analog && (gpio_to_adc(pin) != -EINVAL)){ 485 | *gpio_sel0_for_port[port] = cur_sel0 | (1 << pin_num); 486 | *gpio_sel1_for_port[port] = cur_sel1 | (1 << pin_num); 487 | } else { 488 | *gpio_sel0_for_port[port] = cur_sel0 & ~(1 << pin_num); 489 | *gpio_sel1_for_port[port] = cur_sel1 & ~(1 << pin_num); 490 | } 491 | 492 | } 493 | 494 | /** 495 | * @brief Write a value to a GPIO pin 496 | * 497 | * If the pin has been configured as an output, write the specified state to 498 | * it. If the pin is an input with the pull resistor enabled, it will instead 499 | * set the state to which the pin is pulled. 500 | * 501 | * @param pin The pin to configure. Takes a single byte, whose upper nibble 502 | * specifies the port, and lower nibble specifies the pin. E.g. 503 | * 0x23 translates to port 2, pin 3 504 | * @param value The value to write to the pin 505 | */ 506 | 507 | void gpio_write(int pin, int value) 508 | { 509 | unsigned int port = (pin >> 4) & 0x0F; 510 | unsigned int pin_num = (pin >> 0) & 0x07; 511 | 512 | char cur_dir = *gpio_out_for_port[port]; 513 | 514 | *gpio_out_for_port[port] = value ? cur_dir | (1 << pin_num) : cur_dir & ~(1 << pin_num); 515 | } 516 | 517 | /** 518 | * @brief Read from a GPIO pin 519 | * 520 | * @param pin The pin to configure. Takes a single byte, whose upper nibble 521 | * specifies the port, and lower nibble specifies the pin. E.g. 522 | * 0x23 translates to port 2, pin 3 523 | * @return The state of the GPIO pin's bit in the PxIN register 524 | */ 525 | 526 | unsigned char gpio_read(int pin) 527 | { 528 | unsigned int port = (pin >> 4) & 0x0F; 529 | unsigned int pin_num = (pin >> 0) & 0x07; 530 | 531 | return (*gpio_in_for_port[port] & (1 << pin_num)) != 0; 532 | } 533 | 534 | /** 535 | * @brief Perform a one-byte transfer over SPI 536 | * 537 | * @param b The byte to send 538 | * @return The byte received 539 | */ 540 | static unsigned char spi_xfer(unsigned char b) 541 | { 542 | 543 | /* Wait for previous tx to complete. */ 544 | while (!(UCB1IFG & UCTXIFG)); 545 | 546 | /* Setting TXBUF clears the TXIFG flag. */ 547 | UCB1TXBUF = b; 548 | 549 | /* Wait for a rx character. */ 550 | while (!(UCB1IFG & UCRXIFG)); 551 | 552 | /* Reading clears RXIFG flag. */ 553 | return UCB1RXBUF; 554 | } 555 | 556 | /** 557 | * @brief Initialize UCB1 for SPI operation 558 | * 559 | * Configures UCB1 to operate in SPI mode, with SCLK on P5.2, MISO on P5.1, 560 | * and MOSI on P5.0, with speed controlled by SMLCK and the SPI_CLKDIV macro 561 | */ 562 | void spi_init() 563 | { 564 | /* Put USCI in reset mode, source clock from SMCLK. */ 565 | UCB1CTLW0 = UCSWRST | UCSSEL_2; 566 | 567 | /* SPI mode 0, MSB first, synchronous, master mode, 3 wire SPI*/ 568 | UCB1CTLW0 |= UCCKPH | UCMSB | UCSYNC | UCMST | UCMODE0; 569 | 570 | /* Set pins to SPI mode. (FR5994 version) */ 571 | //SCK P5.2 572 | P5SEL1 &= ~(1 << 2); 573 | P5SEL0 |= (1 << 2); 574 | 575 | //SIMO P5.0 576 | P5SEL1 &= ~(1 << 0); 577 | P5SEL0 |= (1 << 0); 578 | 579 | //SOMI P5.1 580 | P5SEL1 &= ~(1 << 1); 581 | P5SEL0 |= (1 << 1); 582 | 583 | /* Set speed */ 584 | UCB1BR0 = SPI_CLKDIV & 0xFF; 585 | UCB1BR1 = (SPI_CLKDIV >> 8 ) & 0xFF; 586 | 587 | /* Release USCI for operation. */ 588 | UCB1CTLW0 &= ~UCSWRST; 589 | } 590 | 591 | /** 592 | * @brief Send a byte over SPI while ignoring return traffic 593 | */ 594 | void spi_write_byte(unsigned char b) 595 | { 596 | spi_xfer(b); 597 | } 598 | 599 | /** 600 | * @brief Write a buffer over SPI while ignoring return traffic. Blocking. 601 | * 602 | * @param len The number of bytes to write 603 | * @param data Pointer to the bytes to be sent 604 | */ 605 | void spi_write_data(int len, const unsigned char *data) 606 | { 607 | unsigned int i; 608 | for(i = 0; i < len; i++) { 609 | spi_xfer(data[i]); 610 | } 611 | } 612 | 613 | /** 614 | * @brief Read from SPI into a buffer, while sending 0xFF. Blocking. 615 | * 616 | * @param len The number of bytes to send and read 617 | * @param data The buffer into which received data will be written 618 | */ 619 | void spi_read_data(int len, unsigned char *data) 620 | { 621 | unsigned int i; 622 | for(i = 0; i < len; i++) { 623 | data[i] = spi_xfer(0xFF); 624 | } 625 | } 626 | 627 | /** 628 | * @brief Enable a GPIO pin interrupt 629 | * 630 | * Enables an edge-triggered interrupt on a GPIO pin. Does not handle 631 | * configuring the input state of the pin. Clears pending interrupts 632 | * before enabling the new interrupt. Won't do much good unless you have 633 | * set up the appropriate port ISR to handle the pin in question. 634 | * 635 | * @param pin The pin to configure. Takes a single byte, whose upper nibble 636 | * specifies the port, and lower nibble specifies the pin. E.g. 637 | * 0x23 translates to port 2, pin 3 638 | * @param edge Use the RISING or FALLING macro 639 | */ 640 | int enable_pin_interrupt(int pin, int edge) 641 | { 642 | __disable_interrupt(); 643 | unsigned int port = (pin >> 4) & 0x0F; 644 | unsigned int pin_num = (pin >> 0) & 0x07; 645 | 646 | char cur_ies = *gpio_ies_for_port[port]; 647 | *gpio_ies_for_port[port] = edge ? cur_ies | (1 << pin_num) : cur_ies & ~(1 << pin_num); 648 | 649 | char cur_ie = *gpio_ie_for_port[port]; 650 | *gpio_ie_for_port[port] = cur_ie | (1 << pin_num); 651 | 652 | // Clear any pending interrupts on this pin 653 | // You may not want this in the future... 654 | *gpio_ifg_for_port[port] &= ~(1 << pin_num); 655 | 656 | __enable_interrupt(); // enable all interrupts 657 | 658 | return 0; 659 | } 660 | 661 | /** 662 | * @brief Disable a GPIO pin interrupt 663 | * 664 | * Disables all interrupts on a GPIO pin. Does not change the input state or 665 | * interrupt trigger edge of the pin. Clears pending interrupts. 666 | * 667 | * @param pin The pin to configure. Takes a single byte, whose upper nibble 668 | * specifies the port, and lower nibble specifies the pin. E.g. 669 | * 0x23 translates to port 2, pin 3 670 | */ 671 | int disable_pin_interrupt(int pin) 672 | { 673 | __disable_interrupt(); 674 | unsigned int port = (pin >> 4) & 0x0F; 675 | unsigned int pin_num = (pin >> 0) & 0x07; 676 | 677 | char cur_ie = *gpio_ie_for_port[port]; 678 | *gpio_ie_for_port[port] = cur_ie & ~(1 << pin_num); 679 | 680 | // Clear any pending interrupts on this pin 681 | // You may not want this in the future... 682 | *gpio_ifg_for_port[port] &= ~(1 << pin_num); 683 | 684 | __enable_interrupt(); // enable all interrupts 685 | 686 | return 0; 687 | } 688 | --------------------------------------------------------------------------------