pico-tracker/firmware/src/si_trx.c

683 wiersze
19 KiB
C

/*
* Functions for controlling Si Labs Transceivers
* Copyright (C) 2014 Richard Meadows <richardeoin>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "samd20.h"
#include "si_trx.h"
#include "system/port.h"
#include "spi_bitbang.h"
#include "si_trx_defs.h"
#include "hw_config.h"
#define VCXO_FREQUENCY SI4xxx_TCXO_FREQUENCY
/**
* =============================================================================
* Helper Functions
* =============================================================================
*/
/**
* Convenience transfer functions
*/
static void _si_trx_set_property_8(uint8_t group, uint8_t property, uint8_t value)
{
uint8_t buffer[5];
buffer[0] = SI_CMD_SET_PROPERTY;
buffer[1] = group;
buffer[2] = 1;
buffer[3] = property;
buffer[4] = value;
_si_trx_transfer(5, 0, buffer);
}
static void _si_trx_set_property_16(uint8_t group, uint8_t property, uint16_t value)
{
uint8_t buffer[6];
buffer[0] = SI_CMD_SET_PROPERTY;
buffer[1] = group;
buffer[2] = 2;
buffer[3] = property;
buffer[4] = (value >> 8);
buffer[5] = (value);
_si_trx_transfer(6, 0, buffer);
}
static void _si_trx_set_property_24(uint8_t group, uint8_t property, uint32_t value)
{
uint8_t buffer[8];
buffer[0] = SI_CMD_SET_PROPERTY;
buffer[1] = group;
buffer[2] = 3;
buffer[3] = property;
buffer[4] = (value >> 16);
buffer[5] = (value >> 8);
buffer[6] = (value);
_si_trx_transfer(7, 0, buffer);
}
static void _si_trx_set_property_32(uint8_t group, uint8_t property, uint32_t value)
{
uint8_t buffer[8];
buffer[0] = SI_CMD_SET_PROPERTY;
buffer[1] = group;
buffer[2] = 4;
buffer[3] = property;
buffer[4] = (value >> 24);
buffer[5] = (value >> 16);
buffer[6] = (value >> 8);
buffer[7] = (value);
_si_trx_transfer(8, 0, buffer);
}
void _si_trx_transfer_nocts(int tx_count, int rx_count, uint8_t *data)
{
/* Unused */
(void)rx_count;
/* Send command */
_si_trx_cs_enable();
for (int i = 0; i < tx_count; i++) {
spi_bitbang_transfer(data[i]);
}
_si_trx_cs_disable();
}
/**
* Generic SPI Send / Receive
*/
void _si_trx_transfer(int tx_count, int rx_count, uint8_t *data)
{
uint8_t response;
/* Send command */
_si_trx_cs_enable();
for (int i = 0; i < tx_count; i++) {
spi_bitbang_transfer(data[i]);
}
_si_trx_cs_disable();
/**
* Poll CTS. From the docs:
*
* READ_CMD_BUFF is used to poll the CTS signal via the SPI bus. The
* NSEL line should be pulled low, followed by sending the
* READ_CMD_BUFF command on SDI. While NSEL remains asserted low, an
* additional eight clock pulses are sent on SCLK and the CTS
* response byte is read on SDO. If the CTS response byte is not
* 0xFF, the host MCU should pull NSEL high and repeat the polling
* procedure.
*/
do {
for (int i = 0; i < 200; i++); /* Approx. 20µS */
_si_trx_cs_enable();
/* Issue READ_CMD_BUFF */
spi_bitbang_transfer(SI_CMD_READ_CMD_BUFF);
response = spi_bitbang_transfer(0xFF);
/* If the reply is 0xFF, read the response */
if (response == 0xFF) break;
/* Otherwise repeat the procedure */
_si_trx_cs_disable();
} while (1); /* TODO: Timeout? */
/**
* Read response. From the docs:
*
* If the CTS response byte is 0xFF, the host MCU should keep NSEL
* asserted low and provide additional clock cycles on SCLK to read
* out as many response bytes (on SDO) as necessary. The host MCU
* should pull NSEL high upon completion of reading the response
* stream.
*/
for (int i = 0; i < rx_count; i++) {
data[i] = spi_bitbang_transfer(0xFF);
}
_si_trx_cs_disable();
}
/**
* Issues the POWER_UP command
*/
static void si_trx_power_up(uint8_t clock_source, uint32_t xo_freq)
{
uint8_t buffer[7];
buffer[0] = SI_CMD_POWER_UP;
buffer[1] = SI_POWER_UP_FUNCTION;
buffer[2] = clock_source;
buffer[3] = (xo_freq >> 24);
buffer[4] = (xo_freq >> 16);
buffer[5] = (xo_freq >> 8);
buffer[6] = (xo_freq);
_si_trx_transfer(7, 0, buffer);
}
/**
* Gets the 16 bit part number
*/
static uint16_t si_trx_get_part_info(void)
{
uint8_t buffer[3];
buffer[0] = SI_CMD_PART_INFO;
_si_trx_transfer(1, 3, buffer);
return (buffer[1] << 8) | buffer[2];
}
/**
* Clears pending interrupts. Set the corresponding bit low to clear
* the interrupt.
*/
static void si_trx_clear_pending_interrupts(uint8_t packet_handler_clear_pending,
uint8_t chip_clear_pending)
{
uint8_t buffer[4];
buffer[0] = SI_CMD_GET_INT_STATUS;
buffer[1] = packet_handler_clear_pending & ((1<<5)|(1<<1)); /* Mask used bits */
buffer[2] = 0;
buffer[3] = chip_clear_pending;
_si_trx_transfer(4, 0, buffer);
/* This command returns the interrupts status, but we don't use it */
}
/**
* Sets the GPIO configuration for each pin
*/
static void si_trx_set_gpio_configuration(si_gpio_t gpio0, si_gpio_t gpio1,
si_gpio_t gpio2, si_gpio_t gpio3,
uint8_t drive_strength)
{
uint8_t buffer[8];
buffer[0] = SI_CMD_GPIO_PIN_CFG;
buffer[1] = gpio0;
buffer[2] = gpio1;
buffer[3] = gpio2;
buffer[4] = gpio3;
buffer[5] = SI_GPIO_PIN_CFG_NIRQ_MODE_DONOTHING;
buffer[6] = SI_GPIO_PIN_CFG_SDO_MODE_DONOTHING;
buffer[7] = drive_strength;
_si_trx_transfer(8, 0, buffer);
}
/**
* Starts transmitting
*/
static void si_trx_start_tx(uint8_t channel)
{
uint8_t buffer[5];
buffer[0] = SI_CMD_START_TX;
buffer[1] = channel;
buffer[2] = (1 << 4);
buffer[3] = 0;
buffer[4] = 0;
_si_trx_transfer(5, 0, buffer);
}
/**
* Gets readings from the auxillary ADC
*/
static void si_trx_get_adc_reading(uint8_t enable, uint8_t configuration,
uint16_t* gpio_value,
uint16_t* battery_value,
uint16_t* temperature_value)
{
uint8_t buffer[6];
buffer[0] = SI_CMD_GET_ADC_READING;
buffer[1] = enable;
buffer[2] = configuration;
_si_trx_transfer(3, 6, buffer);
*gpio_value = ((buffer[0] & 0x7) << 8) | buffer[1];
*battery_value = ((buffer[2] & 0x7) << 8) | buffer[3];
*temperature_value = ((buffer[4] & 0x7) << 8) | buffer[5];
}
/**
* Returns the measured internal die temperature of the radio
*/
float si_trx_get_temperature(void)
{
uint16_t raw_gpio, raw_battery, raw_temperature;
/* Get the reading from the adc */
si_trx_get_adc_reading(SI_GET_ADC_READING_TEMPERATURE, 0,
&raw_gpio, &raw_battery, &raw_temperature);
return (((float)raw_temperature * 568.0) / 2560.0) - 297.0;
}
/**
* Sets the internal frac-n pll synthesiser divisiors
*/
static void si_trx_frequency_control_set_divider(uint8_t integer_divider,
uint32_t fractional_divider)
{
uint32_t divider = (fractional_divider & 0xFFFFFF) | (integer_divider << 24);
_si_trx_set_property_32(SI_PROPERTY_GROUP_FREQ_CONTROL,
SI_FREQ_CONTROL_INTE,
divider);
}
/**
* Sets the output divider of the frac-n pll synthesiser
*/
static void si_trx_frequency_control_set_band(uint8_t band, uint8_t sy_sel)
{
_si_trx_set_property_8(SI_PROPERTY_GROUP_MODEM,
SI_MODEM_CLKGEN_BAND,
sy_sel | (band & 0x7));
}
/**
* Set the control word of the modem delta-sigma modulator
*/
static void si_trx_modem_set_dsm_ctrl(uint8_t ctrl_word)
{
_si_trx_set_property_8(SI_PROPERTY_GROUP_MODEM,
SI_MODEM_DSM_CTRL,
ctrl_word);
}
/**
* Sets the modem frequency deviation. This is how much the external
* pin deviates the synthesiser from the centre frequency. In units of
* the resolution of the frac-n pll synthsiser.
*
* This is an unsigned 17-bit value.
*/
void si_trx_modem_set_deviation(uint32_t deviation)
{
_si_trx_set_property_24(SI_PROPERTY_GROUP_MODEM,
SI_MODEM_FREQ_DEV,
deviation);
}
static void si_trx_modem_set_tx_datarate(uint32_t rate)
{
uint32_t nco_max_count = ((float)SI4xxx_TCXO_FREQUENCY / 10);
/* Set TX_NCO_MODE */
_si_trx_set_property_32(SI_PROPERTY_GROUP_MODEM,
SI_MODEM_TX_NCO_MODE,
(SI_MODEM_TX_NCO_TXOSR_10X |
(nco_max_count & 0x03FFFFFF)));
/* Set DATA_RATE */
_si_trx_set_property_24(SI_PROPERTY_GROUP_MODEM,
SI_MODEM_DATA_RATE,
rate & 0xFFFFFF);
}
/**
* Writes the coefficients of the modem tx filter
*
*/
static void si_trx_modem_tx_filter_coefficients(uint8_t* coeff_array)
{
uint8_t buffer[4+9];
buffer[0] = SI_CMD_SET_PROPERTY;
buffer[1] = SI_PROPERTY_GROUP_MODEM; // group
buffer[2] = 9;
buffer[3] = SI_MODEM_TX_FILTER_COEFF8; // prop
/* Write filter coefficents 8 to 0 */
for (int i = 0; i < 9; i++) {
buffer[4+i] = coeff_array[8-i];
}
_si_trx_transfer(4+9, 0, buffer);
}
/**
* Sets the modem frequency offset manually. In units of the
* resolution of the frac-n pll synthsiser.
*
* This is a signed 16-bit value.
*/
void si_trx_modem_set_offset(int16_t offset)
{
/* _si_trx_set_property_16(SI_PROPERTY_GROUP_MODEM, */
/* SI_MODEM_FREQ_OFFSET, */
/* offset); */
uint8_t buffer[6];
buffer[0] = SI_CMD_SET_PROPERTY;
buffer[1] = SI_PROPERTY_GROUP_MODEM; // group
buffer[2] = 2;
buffer[3] = SI_MODEM_FREQ_OFFSET; // prop
buffer[4] = (offset >> 8);
buffer[5] = (offset);
_si_trx_transfer_nocts(6, 0, buffer);
}
/**
* Sets the modulation mode
*/
static void si_trx_modem_set_modulation(uint8_t tx_direct_mode,
uint8_t tx_direct_gpio,
uint8_t tx_modulation_source,
uint8_t modulation_type)
{
_si_trx_set_property_8(SI_PROPERTY_GROUP_MODEM, SI_MODEM_MOD_TYPE,
tx_direct_mode | tx_direct_gpio |
tx_modulation_source | modulation_type);
}
/**
* Sets the tx power
*/
static void si_trx_set_tx_power(uint8_t tx_power)
{
_si_trx_set_property_8(SI_PROPERTY_GROUP_PA, SI_PA_PWR_LVL, tx_power);
}
/**
* Set the duty cycle of the tx power amplifier drive signal
*/
static void si_trx_set_tx_pa_duty_cycle(uint8_t pa_duty_cycle)
{
_si_trx_set_property_8(SI_PROPERTY_GROUP_PA, SI_PA_BIAS_CLKDUTY, pa_duty_cycle);
}
/**
* Pre-calculates sythesiser paramters for use with si_trx_set_frequency
* Might be called before module is initialised, so should just contain math
*
* frequency: Floating-point value for the frequency
*/
void si_trx_get_frequency_configuration(struct si_frequency_configuration* config,
uint32_t frequency)
{
/* Higher frequency resolution, but also higher power (~+200µA) */
config->nprescaler = 2;
if (frequency < 705000000UL) {
config->outdiv = 6; config->band = SI_MODEM_CLKGEN_FVCO_DIV_6;
}
if (frequency < 525000000UL) {
config->outdiv = 8; config->band = SI_MODEM_CLKGEN_FVCO_DIV_8;
}
if (frequency < 353000000UL) {
config->outdiv = 12; config->band = SI_MODEM_CLKGEN_FVCO_DIV_12;
}
if (frequency < 239000000UL) {
config->outdiv = 16; config->band = SI_MODEM_CLKGEN_FVCO_DIV_16;
}
if (frequency < 177000000UL) {
config->outdiv = 24; config->band = SI_MODEM_CLKGEN_FVCO_DIV_24;
}
float f_pfd = config->nprescaler * VCXO_FREQUENCY / config->outdiv;
config->n = ((uint16_t)(frequency / f_pfd)) - 1;
float ratio = (float)frequency / f_pfd;
float rest = ratio - (float)config->n;
config->m = (uint32_t)(rest * (float)(1 << 19));
/* Check n and m are in valid ranges, halt otherwise */
if (config->n > 0x7f || config->m > 0xfffff) while (1);
/* Calculate the LSB tuning resolution of the frac-n pll synthesiser. */
config->lsb_tuning_resolution = f_pfd / (float)(1 << 19);
}
/**
* Writes a pre-calculated frequency configuration to the si
* synthesiser. Also sets deviation
*/
static void si_trx_set_frequency(struct si_frequency_configuration* config,
uint16_t deviation)
{
/* Set the frac-n PLL output divider */
if (config->nprescaler == 4) { /* Prescaler */
si_trx_frequency_control_set_band(config->band, SI_MODEM_CLKGEN_SY_SEL_0);
} else { /* Default Mode */
si_trx_frequency_control_set_band(config->band, SI_MODEM_CLKGEN_SY_SEL_1);
}
/* Set the frac-n PLL divisior */
si_trx_frequency_control_set_divider(config->n, config->m);
/* Set the modem dsm control word. Allow even deviation values */
si_trx_modem_set_dsm_ctrl(SI_MODEM_DSM_CTRL_NOFORCE_DSM_LSB |
SI_MODEM_DSM_CTRL_MASH_1_1_1);
/* Set the modem frequency deviation (for the external pin)*/
si_trx_modem_set_deviation(deviation);
}
/**
* Resets the transceiver
*/
void si_trx_reset(uint8_t modulation_type, struct si_frequency_configuration* fconfig,
uint16_t deviation, uint8_t power, enum si_filter_model filter)
{
/* We expect to already be shutdown */
_si_trx_sdn_enable(); /* active high shutdown = reset */
for (int i = 0; i < 15; i++); /* a few microseconds */
_si_trx_sdn_disable(); /* booting. expected to take 15ms */
/* Poll for part number */
while (si_trx_get_part_info() != 17512);
/* Power Up */
si_trx_power_up(SI_POWER_UP_TCXO, VCXO_FREQUENCY);
/* Clear pending interrupts */
si_trx_clear_pending_interrupts(0, 0);
/* Disable all interrupts */
_si_trx_set_property_8(SI_PROPERTY_GROUP_INT_CTL, SI_INT_CTL_ENABLE, 0);
/* Configure GPIOs */
si_trx_set_gpio_configuration(SI_GPIO_PIN_CFG_GPIO_MODE_INPUT,
SI_GPIO_PIN_CFG_GPIO_MODE_INPUT | SI_GPIO_PIN_CFG_PULL_ENABLE,
SI_GPIO_PIN_CFG_GPIO_MODE_INPUT,
SI_GPIO_PIN_CFG_GPIO_MODE_INPUT | SI_GPIO_PIN_CFG_PULL_ENABLE,
SI_GPIO_PIN_CFG_DRV_STRENGTH_LOW);
si_trx_set_frequency(fconfig, deviation);
si_trx_set_tx_power(power);
/* Modem tx filter coefficients */
uint8_t rsid_si_coeff[] = {0x1, 0x4, 0x9, 0x11, 0x19, 0x22, 0x2a, 0x30, 0x31};
uint8_t p_si_coeff[] = {0x6, 0x8, 0x1, 0xf2, 0xe4, 0xe7, 0xff, 0x1d, 0x2b};
switch (filter) {
case SI_FILTER_APRS:
/**
* Modem tx filter coefficients for APRS
* 0dB @ 2.2kHz
* -6dB @ 1.2kHz (for pre-emphasis)
* < -30dB from 3.6KHz
*/
si_trx_modem_set_tx_datarate(1600); /* Filter sampling rate 1600*10 = 16kHz */
si_trx_modem_tx_filter_coefficients(p_si_coeff);
break;
case SI_FILTER_RSID:
/**
* Modem tx filter coefficients for RSID
* -6dB @ dc (for ±0.5 deviation max)
* < -50dB from 1.8KHz
*/
si_trx_modem_set_tx_datarate(1600); /* Filter sampling rate 1600*10 = 16kHz */
si_trx_modem_tx_filter_coefficients(rsid_si_coeff);
break;
default: /* Just leave the defaults from startup in place */
break;
}
/* Set modulation type */
si_trx_modem_set_modulation(SI_MODEM_MOD_DIRECT_MODE_SYNC, // ASYNC
SI_MODEM_MOD_GPIO_1,
SI_MODEM_MOD_SOURCE_DIRECT,
modulation_type);
si_trx_state_tx_tune();
}
/**
* Enables the radio and starts transmitting
*/
void si_trx_on(uint8_t modulation_type, struct si_frequency_configuration* fconfig,
uint16_t deviation, uint8_t power, enum si_filter_model filter)
{
si_trx_reset(modulation_type, fconfig, deviation, power, filter);
si_trx_start_tx(0);
}
/**
* Disables the radio and places it in shutdown
*/
void si_trx_off(void)
{
si_trx_state_ready();
/* Physical shutdown */
_si_trx_sdn_enable();
}
/**
* Resets the radio
*/
void si_trx_shutdown(void)
{
/* Configure the SDN pin */
port_pin_set_config(SI4xxx_SDN_PIN,
PORT_PIN_DIR_OUTPUT, /* Direction */
PORT_PIN_PULL_NONE, /* Pull */
false); /* Powersave */
/* Put the transciever in shutdown */
_si_trx_sdn_enable();
}
/**
* Initialises the radio interface to the radio
*/
void si_trx_init(void)
{
si_trx_shutdown();
/* Configure the SPI select pin */
port_pin_set_config(SI4xxx_SEL_PIN,
PORT_PIN_DIR_OUTPUT, /* Direction */
PORT_PIN_PULL_NONE, /* Pull */
false); /* Powersave */
/* Put the SEL pin in reset */
_si_trx_cs_disable();
/* Configure the GPIO pins */
port_pin_set_config(SI4xxx_GPIO0_PIN,
PORT_PIN_DIR_OUTPUT, /* Direction */
PORT_PIN_PULL_NONE, /* Pull */
false); /* Powersave */
port_pin_set_output_level(SI4xxx_GPIO0_PIN, 0);
port_pin_set_config(SI4xxx_GPIO1_PIN,
PORT_PIN_DIR_OUTPUT, /* Direction */
PORT_PIN_PULL_NONE, /* Pull */
false); /* Powersave */
port_pin_set_output_level(SI4xxx_GPIO1_PIN, 0);
/* Configure the IRQ pin */
// TODO
/* Configure the serial port */
spi_bitbang_init(SI4xxx_SERCOM_MOSI_PIN,
SI4xxx_SERCOM_MISO_PIN,
SI4xxx_SERCOM_SCK_PIN);
}
/**
* Quick and dirty loopback test. Should print 0x34
*/
void spi_loopback_test(void)
{
/* spi_init(SI_TRX_SERCOM, */
/* SPI_MODE_MASTER, /\** SPI mode *\/ */
/* SPI_DATA_ORDER_MSB, /\** Data order *\/ */
/* SPI_TRANSFER_MODE_0, /\** Transfer mode *\/ */
/* SI4xxx_SERCOM_MUX, /\** Mux setting *\/ */
/* SPI_CHARACTER_SIZE_8BIT, /\** SPI character size *\/ */
/* false, /\** Enabled in sleep *\/ */
/* true, /\** Enable receiver *\/ */
/* 100000, /\** Master - Baud rate *\/ */
/* 0, /\** Slave - Frame format *\/ */
/* 0, /\** Slave - Address mode *\/ */
/* 0, /\** Slave - Address *\/ */
/* 0, /\** Slave - Address mask *\/ */
/* false, /\** Slave - Preload data *\/ */
/* GCLK_GENERATOR_0, /\** GCLK generator to use *\/ */
/* SI4xxx_SERCOM_MOSI_PINMUX, /\** Pinmux *\/ */
/* SI4xxx_SERCOM_MISO_PINMUX, /\** Pinmux *\/ */
/* SI4xxx_SERCOM_SCK_PINMUX, /\** Pinmux *\/ */
/* PINMUX_UNUSED); /\** Pinmux *\/ */
/* spi_enable(SI_TRX_SERCOM); */
/* Init loopback */
spi_bitbang_init(SI4xxx_SERCOM_MOSI_PIN,
SI4xxx_SERCOM_MOSI_PIN,
SI4xxx_SERCOM_SCK_PIN);
/* Enable */
/* Test transfer */
uint8_t data = spi_bitbang_transfer(0x34);
/* Print result */
//semihost_printf("Rx'ed: 0x%02x\n", data);
}