You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1144 lines
30 KiB
1144 lines
30 KiB
/*
|
|
* A sample application transmitting AFSK 1200 baud
|
|
*
|
|
* Portions Copyright (C) 2018 Libre Space Foundation
|
|
* Portions Copyright (C) 2018 Jonathan Brandenburg
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include "ax25.h"
|
|
#include "ax5043.h"
|
|
#include "status.h"
|
|
#include "utils.h"
|
|
#include "spi/ax5043spi.h"
|
|
|
|
static uint8_t single_fifo_access = 0;
|
|
|
|
static uint8_t __tx_buf[MAX_FRAME_LEN];
|
|
static size_t __tx_buf_idx = 0;
|
|
static uint8_t __tx_fifo_chunk[AX5043_FIFO_MAX_SIZE];
|
|
static uint32_t __tx_remaining = 0;
|
|
|
|
extern uint32_t tx_freq_hz;
|
|
|
|
/**
|
|
* FIFO command for the preamble. The third byte corresponds the length of
|
|
* the preamble and is set by the TX routine for every frame
|
|
*/
|
|
static uint8_t __preamble_cmd[4] = { AX5043_FIFO_REPEATDATA_CMD,
|
|
AX5043_FIFO_PKTSTART | AX5043_FIFO_RAW | AX5043_FIFO_NOCRC, 0,
|
|
AX25_SYNC_FLAG };
|
|
|
|
/**
|
|
* FIFO command for the postable. The third byte corresponds the length of
|
|
* the postable and is set by the TX routine for every frame
|
|
*/
|
|
static uint8_t __postamble_cmd[4] = {
|
|
AX5043_FIFO_REPEATDATA_CMD,
|
|
AX5043_FIFO_PKTSTART | AX5043_FIFO_PKTEND | AX5043_FIFO_RAW | AX5043_FIFO_NOCRC,
|
|
0, AX25_SYNC_FLAG };
|
|
|
|
/**
|
|
* Indicates if a TX is currently active
|
|
*/
|
|
static volatile uint8_t __tx_active = 0;
|
|
|
|
static ax5043_conf_t *__ax5043_conf = NULL;
|
|
|
|
static inline int set_tx_black_magic_regs();
|
|
|
|
/**
|
|
* Checks if the AX5043 handler is valid
|
|
* @param conf the AX5043 configuration handler pointer
|
|
* @return 1 if it is valid 0 otherwise
|
|
*/
|
|
static uint8_t is_ax5043_conf_valid(ax5043_conf_t *conf) {
|
|
if (!conf || !conf->f_xtal) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Resets the AX5043
|
|
* @param conf the AX5043 configuration handler
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_reset_a(ax5043_conf_t *conf) {
|
|
int ret;
|
|
uint8_t val;
|
|
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
conf->rf_init = 0;
|
|
usleep(100);
|
|
|
|
/* Reset the chip using the appropriate register */
|
|
val = BIT(7);
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_PWRMODE, val);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
usleep(100);
|
|
/* Clear the reset bit, but keep REFEN and XOEN */
|
|
ret = ax5043_spi_read_8(conf, &val, AX5043_REG_PWRMODE);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
val &= (BIT(6) | BIT(5));
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_PWRMODE, val);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
usleep(100);
|
|
|
|
ret = ax5043_set_power_mode(conf, POWERDOWN);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Initialization routine for the AX5043 IC
|
|
* @param conf the AX5043 configuration handler
|
|
* @param spi the SPI handler
|
|
* @param f_xtal the frequency of the crystal or the TCXO
|
|
* @param vco the VCO mode
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_init(ax5043_conf_t *conf, uint32_t f_xtal, vco_mode_t vco) {
|
|
int ret;
|
|
uint8_t revision;
|
|
uint8_t val;
|
|
|
|
if (!conf) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
/* Set the initial parameters */
|
|
memset(conf, 0, sizeof(ax5043_conf_t));
|
|
|
|
switch (vco) {
|
|
case VCO_INTERNAL:
|
|
case VCO_EXTERNAL:
|
|
conf->vco = vco;
|
|
break;
|
|
default:
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
conf->rf_init = 0;
|
|
conf->freqsel = -1;
|
|
conf->f_xtal = f_xtal;
|
|
if (conf->f_xtal > 24800000) {
|
|
conf->f_xtaldiv = 2;
|
|
} else {
|
|
conf->f_xtaldiv = 1;
|
|
}
|
|
|
|
ret = ax5043_reset_a(conf);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Try first to read the revision register of the AX5043 */
|
|
ret = ax5043_spi_read_8(conf, &revision, AX5043_REG_REV);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
if (revision != AX5043_REV) {
|
|
return -PQWS_NO_RF_FOUND;
|
|
}
|
|
|
|
/* To ensure communication try to write and read the scratch register */
|
|
val = AX5043_SCRATCH_TEST;
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_SCRATCH, val);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
val = 0x0;
|
|
ret = ax5043_spi_read_8(conf, &val, AX5043_REG_SCRATCH);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
if (val != AX5043_SCRATCH_TEST) {
|
|
return -PQWS_NO_RF_FOUND;
|
|
}
|
|
|
|
ret = ax5043_set_pll_params(conf);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Write the performance register F35 based on the XTAL frequency */
|
|
if (conf->f_xtaldiv == 1) {
|
|
ret = ax5043_spi_write_8(conf, 0xF35, 0x10);
|
|
} else {
|
|
ret = ax5043_spi_write_8(conf, 0xF35, 0x11);
|
|
}
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* FIFO maximum chunk */
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_PKTCHUNKSIZE,
|
|
AX5043_PKTCHUNKSIZE_240);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Set RF parameters */
|
|
ret = ax5043_freqsel(conf, FREQA_MODE);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* We use APRS for all transmitted frames. APRS is encapsulated in a
|
|
* AX.25 frame. For 9600 baudrate is FSK9600 G3RUH compatible modem
|
|
*/
|
|
ret = ax5043_aprs_framing_setup(conf);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Setup TX only related parameters */
|
|
ret = ax5043_conf_tx_path(conf);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Set an internal copy for the ax5042_wait_for_transmit function */
|
|
__ax5043_conf = conf;
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
ax5043_enable_pwramp(conf, AX5043_EXT_PA_DISABLE);
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Performs TX specific configuration of the AX5043
|
|
* @param conf the AX5043 configuration handler
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_conf_tx_path(ax5043_conf_t *conf) {
|
|
int ret;
|
|
|
|
ret = ax5043_set_tx_synth(conf);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ax5043_set_tx_baud(conf, TX_BAUDRATE);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ax5043_set_tx_freq(conf, tx_freq_hz);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Our TX is on single ended mode */
|
|
//ret = ax5043_spi_write_8(conf, AX5043_REG_MODCFGA,
|
|
//AX5043_TX_SINGLE_ENDED);
|
|
//if (ret) {
|
|
// return ret;
|
|
//}
|
|
|
|
/* Our TX is on double ended mode */
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_MODCFGA,
|
|
AX5043_TX_DIFFERENTIAL);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Set the rest of the performance registers for TX */
|
|
ret = set_tx_black_magic_regs(conf);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* As our board has an external PA, reduce the output power to reduce
|
|
* the excess bandwidth emissions
|
|
*/
|
|
//ret = ax5043_spi_write_16(conf, AX5043_REG_TXPWRCOEFFB1, 0x01FF);
|
|
//if (ret) {
|
|
//return ret;
|
|
//}
|
|
|
|
// Not using a PA, transmit half power
|
|
ret = ax5043_spi_write_16(conf, AX5043_REG_TXPWRCOEFFB1, 0x07FF);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Sets the power mode of the AX5043
|
|
* @param conf the AX5043 configuration handler
|
|
* @param mode the power mode
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_set_power_mode(ax5043_conf_t *conf, power_mode_t mode) {
|
|
int ret;
|
|
uint8_t val;
|
|
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
/* Read the contents of the register */
|
|
ret = ax5043_spi_read_8(conf, &val, AX5043_REG_PWRMODE);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Keep REFEN and XOEN values */
|
|
val &= (BIT(6) | BIT(5));
|
|
|
|
switch (mode) {
|
|
case POWERDOWN:
|
|
val |= AX5043_POWERDOWN;
|
|
break;
|
|
case DEEPSLEEP:
|
|
val |= AX5043_DEEPSLEEP;
|
|
break;
|
|
case STANDBY:
|
|
val |= AX5043_STANDBY;
|
|
break;
|
|
case FIFO_ENABLED:
|
|
val |= AX5043_FIFO_ENABLED;
|
|
break;
|
|
case RECEIVE_MODE:
|
|
val |= AX5043_RECEIVE_MODE;
|
|
break;
|
|
case RECEIVER_RUNNING:
|
|
val |= AX5043_RECEIVER_RUNNING;
|
|
break;
|
|
case RECEIVER_WOR:
|
|
val |= AX5043_RECEIVER_WOR;
|
|
break;
|
|
case TRANSMIT_MODE:
|
|
val |= AX5043_TRANSMIT_MODE;
|
|
break;
|
|
case FULLTX:
|
|
val |= AX5043_FULLTX;
|
|
break;
|
|
default:
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
return ax5043_spi_write_8(conf, AX5043_REG_PWRMODE, val);
|
|
}
|
|
|
|
/**
|
|
* Sets the RF frequency of the TX. If the previous TX frequency is
|
|
* further enough than the new one, this function performs automatically
|
|
* auto-ranging.
|
|
*
|
|
* @param conf the AX5043 configuration handler
|
|
* @param freq the target RF frequency
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_set_tx_freq(ax5043_conf_t *conf, uint32_t freq) {
|
|
int ret;
|
|
uint32_t prev_freq;
|
|
uint32_t reg_val;
|
|
uint8_t rfdiv = 0;
|
|
uint8_t pllcodediv = 0;
|
|
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
/* Check the frequency range. The actual range depends on the VCO used */
|
|
switch (conf->vco) {
|
|
case VCO_INTERNAL:
|
|
if (freq >= MIN_RF_FREQ_INT_VCO_RFDIV0
|
|
&& freq <= MAX_RF_FREQ_INT_VCO_RFDIV0) {
|
|
rfdiv = AX5043_RFDIV0;
|
|
} else if (freq >= MIN_RF_FREQ_INT_VCO_RFDIV1
|
|
&& freq <= MAX_RF_FREQ_INT_VCO_RFDIV1) {
|
|
rfdiv = AX5043_RFDIV1;
|
|
} else {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
break;
|
|
case VCO_EXTERNAL:
|
|
fprintf(stderr, "ERROR: Unexpected use of external VCO\n");
|
|
if (freq >= MIN_RF_FREQ_EXT_VCO_RFDIV0
|
|
&& freq <= MAX_RF_FREQ_EXT_VCO_RFDIV0) {
|
|
rfdiv = AX5043_RFDIV0;
|
|
} else if (freq >= MIN_RF_FREQ_EXT_VCO_RFDIV1
|
|
&& freq <= MAX_RF_FREQ_EXT_VCO_RFDIV1) {
|
|
rfdiv = AX5043_RFDIV1;
|
|
} else {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
break;
|
|
default:
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
prev_freq = conf->tx_freq;
|
|
pllcodediv = rfdiv | (uint8_t) (conf->vco << 4);
|
|
|
|
// Added by Jonathan Brandenburg
|
|
// Have an external inductor
|
|
pllcodediv |= BIT(5);
|
|
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_PLLVCODIV, pllcodediv);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Write properly the F34 performance register based on the RFDIV*/
|
|
if (rfdiv == AX5043_RFDIV1) {
|
|
ret = ax5043_spi_write_8(conf, 0xF34, 0x28);
|
|
} else {
|
|
ret = ax5043_spi_write_8(conf, 0xF34, 0x08);
|
|
}
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Set the RF frequency
|
|
* Frequency should be avoided to be a multiple integer of the crystal
|
|
* frequency, so we always set to 1 the LSB
|
|
*/
|
|
reg_val = ((uint32_t) (((float) freq / (float) conf->f_xtal) * (1 << 24))
|
|
| 0x1);
|
|
if (conf->freqsel == FREQA_MODE) {
|
|
ret = ax5043_spi_write_32(conf, AX5043_REG_FREQA3, reg_val);
|
|
} else {
|
|
ret = ax5043_spi_write_32(conf, AX5043_REG_FREQB3, reg_val);
|
|
}
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Considered that the frequency successfully changed */
|
|
conf->tx_freq = freq;
|
|
|
|
/* If the frequency difference is great enough perform autoranging */
|
|
if (freq + 25000000 > prev_freq || freq - 25000000 < prev_freq) {
|
|
ax5043_autoranging(conf);
|
|
}
|
|
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Set the TX baudrate
|
|
* @param conf the AX5043 configuration handler
|
|
* @param baud the baudrate
|
|
* @return 0 on success or negative error code
|
|
*/
|
|
int ax5043_set_tx_baud(ax5043_conf_t *conf, uint32_t baud) {
|
|
int ret = PQWS_SUCCESS;
|
|
uint32_t val;
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
val = (uint32_t) ((((float) baud) / (float) conf->f_xtal) * (1 << 24))
|
|
| 0x1;
|
|
ret = ax5043_spi_write_24(conf, AX5043_REG_TXRATE2, val);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
conf->tx_baudrate = baud;
|
|
|
|
/* Set the deviation to standard 3 KHz for FM */
|
|
// For AFSK, FSKDEV = 0.858785 * fDeviation / fXTAL * 2^24
|
|
//
|
|
val = (uint32_t) ((0.858785 * 3000.0f / (float) conf->f_xtal) * (1 << 24))
|
|
| 0x1;
|
|
ret = ax5043_spi_write_24(conf, AX5043_REG_FSKDEV2, val);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Sets the currently used frequency registers (A or B)
|
|
* @param conf the AX5043 configuration handler
|
|
* @param f the frequency mode (A or B)
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_freqsel(ax5043_conf_t *conf, freq_mode_t f) {
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
if (f != FREQA_MODE && f != FREQB_MODE) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
conf->freqsel = f;
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Sets the TX frequency synthesizer related configuration registers.
|
|
* @param conf the AX5043 configuration handler
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_set_tx_synth(ax5043_conf_t *conf) {
|
|
int ret;
|
|
uint8_t val;
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
switch (conf->freqsel) {
|
|
case FREQA_MODE:
|
|
val = 0x0;
|
|
break;
|
|
case FREQB_MODE:
|
|
val = 1 << 7;
|
|
break;
|
|
default:
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
/* Bypass external filter and use 100 kHZ loop bandwidth */
|
|
val |= BIT(3) | BIT(0);
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_PLLLOOP, val);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Set the charge pump current based on the loop bandwidth
|
|
* 68 uA @ 100 kHZ
|
|
*/
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_PLLCPI, (uint8_t) (68 / 8.5));
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_XTALCAP, 0);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Sets the PLL related configuration registers.
|
|
* @param conf the AX5043 configuration handler
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_set_pll_params(ax5043_conf_t *conf) {
|
|
int ret;
|
|
uint8_t i = 8;
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
/* Set VCO to manual */
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_PLLVCOI,
|
|
AX5043_PLLVCOI_MANUAL | (1250 / 50));
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* According to the manual PLL ranging clock should be less than 1/10
|
|
* of the PLL loop bandwidth. The smallest PLL bandwidth configuration
|
|
* is 100 kHz.
|
|
*/
|
|
// This this next line contains an error
|
|
//while (conf->f_xtal / (uint32_t) (1 << i) > 10000) {
|
|
while (conf->f_xtal / (uint32_t) (1 << i) > 100000) {
|
|
i++;
|
|
}
|
|
i = i > 15 ? 15 : i;
|
|
i = i < 8 ? 8 : i;
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_PLLRNGCLK, (uint8_t) (i - 8));
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Performs auto-ranging using the frequency registers configured by
|
|
* ax5043_freqsel().
|
|
*
|
|
* @param conf the AX5043 configuration handler
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_autoranging(ax5043_conf_t *conf) {
|
|
int ret = PQWS_SUCCESS;
|
|
uint16_t pllranging_reg;
|
|
uint8_t val = 0;
|
|
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
switch (conf->freqsel) {
|
|
case FREQA_MODE:
|
|
pllranging_reg = AX5043_REG_PLLRANGINGA;
|
|
break;
|
|
case FREQB_MODE:
|
|
pllranging_reg = AX5043_REG_PLLRANGINGB;
|
|
break;
|
|
default:
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
/* Write the initial VCO setting and start autoranging */
|
|
val = BIT(4) | AX5043_VCOR_INIT;
|
|
ret = ax5043_spi_write_8(conf, pllranging_reg, val);
|
|
if (ret) {
|
|
printf("ERROR: AX5043 Autoranging Write Failure\n\n");
|
|
return ret;
|
|
}
|
|
|
|
usleep(10);
|
|
//val = 0;
|
|
/* Wait until the autoranging is complete */
|
|
int timeout = 0;
|
|
clock_t start = clock();
|
|
while (((val & BIT(4)) != 0) && !timeout ) { // changed to !=, since https://www.onsemi.com/pub/Collateral/AND9347-D.PDF says BIT(4) RNG START clears when autoranging done
|
|
ret = ax5043_spi_read_8(conf, &val, pllranging_reg);
|
|
if (ret) {
|
|
printf("ERROR: AX5043 Autoranging Read Failure\n\n");
|
|
return ret;
|
|
}
|
|
if ((clock() - start) > 1000000) {
|
|
timeout = 1;
|
|
}
|
|
}
|
|
|
|
if (val & BIT(5)) {
|
|
printf("ERROR: AX5043 Autoranging Error\n\n");
|
|
return -PQWS_AX5043_AUTORANGING_ERROR;
|
|
} else if (timeout) {
|
|
printf("ERROR: AX5043 Autoranging Timeout\n\n");
|
|
return -1;
|
|
}
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param conf the AX5043 configuration handler
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_aprs_framing_setup(ax5043_conf_t *conf) {
|
|
int ret = PQWS_SUCCESS;
|
|
uint8_t val = 0;
|
|
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
/* Set modulation */
|
|
val = AX5043_MODULATION_AFSK;
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_MODULATION, val);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
// To set the space frequency, 1070
|
|
// 1070 * 2^18 / fXTAL -> 1070 * 2^18 / 16000000 -> 18 -> 0x12
|
|
|
|
// To set the space frequency, 1200
|
|
// 1200 * 2^18 / fXTAL -> 1200 * 2^18 / 16000000 -> 20 -> 0x14
|
|
|
|
ret = ax5043_spi_write_16(conf, AX5043_REG_AFSKSPACE1, 0x24);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
// To set the mark frequency, 1270
|
|
// 1270 * 2^18 / fXTAL -> 1270 * 2^18 / 16000000 -> 21 -> 0x15
|
|
|
|
// To set the mark frequency, 1400
|
|
// 1400 * 2^18 / fXTAL -> 1400 * 2^18 / 16000000 -> 23 -> 0x17
|
|
|
|
ret = ax5043_spi_write_16(conf, AX5043_REG_AFSKMARK1, 0x14);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* As we do not use any external filter, try to filter from
|
|
* the AX5043 the signal
|
|
*/
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_MODCFGF,
|
|
AX5043_FREQSHAPE_GAUSSIAN_BT_05);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Set HDLC encoding: Differential = 1, Inverse = 1, Scrambling = 1 */
|
|
//ax5043_spi_write_8(conf, AX5043_REG_ENCODING,
|
|
/* Set HDLC encoding: Differential = 1, Inverse = 1, Scrambling = 1 */
|
|
ax5043_spi_write_8(conf, AX5043_REG_ENCODING,
|
|
AX5043_ENC_DIFF | AX5043_ENC_INV);
|
|
|
|
/* HDLC framing */
|
|
ax5043_spi_write_8(conf, AX5043_REG_FRAMING,
|
|
AX5043_HDLC_FRAMING | AX5043_CRC16_CCITT);
|
|
return ret;
|
|
}
|
|
|
|
static int __tx_frame_end(ax5043_conf_t *conf) {
|
|
int ret;
|
|
|
|
ax5043_enable_pwramp(conf, AX5043_EXT_PA_DISABLE);
|
|
|
|
/* Set AX5043 to power down mode */
|
|
ret = ax5043_set_power_mode(conf, POWERDOWN);
|
|
__tx_active = 0;
|
|
return ret;
|
|
}
|
|
|
|
static int __tx_frame(ax5043_conf_t *conf, const uint8_t *in, uint32_t len,
|
|
uint8_t preamble_len, uint8_t postamble_len, uint32_t timeout_ms) {
|
|
int ret = PQWS_SUCCESS;
|
|
uint8_t data_cmd[3] = { AX5043_FIFO_VARIABLE_DATA_CMD, 0, 0 };
|
|
size_t chunk_size = 0;
|
|
size_t avail;
|
|
uint8_t val;
|
|
uint32_t start = millis();
|
|
|
|
/*
|
|
* Apply preamble and postamble repetition length. Rest of the fields should
|
|
* remain unaltered
|
|
*/
|
|
__preamble_cmd[2] = preamble_len;
|
|
__postamble_cmd[2] = postamble_len;
|
|
|
|
memcpy(__tx_fifo_chunk, __preamble_cmd, sizeof(__preamble_cmd));
|
|
chunk_size = sizeof(__preamble_cmd);
|
|
__tx_buf_idx = 0;
|
|
|
|
/*
|
|
* Always leave some space for the postamble. This greatly reduces the
|
|
* complexity of dealing with some corner cases
|
|
*/
|
|
avail = min_ul(
|
|
AX5043_FIFO_MAX_SIZE - sizeof(__preamble_cmd) - sizeof(data_cmd)
|
|
- sizeof(__postamble_cmd), len);
|
|
if (len == avail) {
|
|
data_cmd[1] = (uint8_t) (len + 1);
|
|
data_cmd[2] = AX5043_FIFO_PKTEND;
|
|
__tx_remaining = 0;
|
|
memcpy(__tx_fifo_chunk + chunk_size, data_cmd, sizeof(data_cmd));
|
|
chunk_size += sizeof(data_cmd);
|
|
memcpy(__tx_fifo_chunk + chunk_size, in, len);
|
|
chunk_size += len;
|
|
/*
|
|
* At this point we are sure that the whole frame + postamble can fit in
|
|
* the FIFO chunk
|
|
*/
|
|
memcpy(__tx_fifo_chunk + chunk_size, __postamble_cmd,
|
|
sizeof(__postamble_cmd));
|
|
chunk_size += sizeof(__postamble_cmd);
|
|
single_fifo_access = 1;
|
|
} else {
|
|
data_cmd[1] = (uint8_t) (avail + 1);
|
|
data_cmd[2] = 0;
|
|
memcpy(__tx_fifo_chunk + chunk_size, data_cmd, sizeof(data_cmd));
|
|
chunk_size += sizeof(data_cmd);
|
|
memcpy(__tx_fifo_chunk + chunk_size, in, avail);
|
|
chunk_size += avail;
|
|
|
|
memcpy(__tx_buf, in + avail, len - avail);
|
|
__tx_remaining = (uint32_t) (len - avail);
|
|
single_fifo_access = 0;
|
|
}
|
|
|
|
/* Set AX5043 to FULLTX mode */
|
|
ret = ax5043_set_power_mode(conf, FULLTX);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ax5043_spi_wait_xtal(conf, 100);
|
|
|
|
/* Wait for the FIFO to become ready */
|
|
val = 0;
|
|
while (!val) {
|
|
ax5043_spi_read_8(conf, &val, AX5043_REG_POWSTAT);
|
|
/* Select only the modem power state */
|
|
val &= AX5043_SVMODEM;
|
|
if (millis() - start > timeout_ms) {
|
|
ret = -PQWS_TIMEOUT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Fire-up the first data to the FIFO */
|
|
ret = ax5043_spi_write(conf, AX5043_REG_FIFODATA, __tx_fifo_chunk,
|
|
(uint32_t) chunk_size);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
__tx_active = 1;
|
|
/* Commit to FIFO ! */
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_FIFOSTAT, AX5043_FIFO_COMMIT_CMD);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ax5043_tx_frame(ax5043_conf_t *conf, const uint8_t *in, uint32_t len,
|
|
uint8_t preamble_len, uint8_t postamble_len, uint32_t timeout_ms) {
|
|
int ret = 0;
|
|
|
|
/* Wait for the previous frame to be transmitted */
|
|
while (__tx_active) {
|
|
ret++;
|
|
}
|
|
|
|
ret = ax5043_enable_pwramp(conf, AX5043_EXT_PA_ENABLE);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = __tx_frame(conf, in, len, preamble_len, postamble_len, timeout_ms);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Wait the crystal to become ready
|
|
* @param conf the AX5043 configuration handler
|
|
* @param timeout_ms the timeout in milliseconds
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_spi_wait_xtal(ax5043_conf_t *conf, uint32_t timeout_ms) {
|
|
int ret;
|
|
uint8_t val = 0x0;
|
|
uint32_t start = millis();
|
|
|
|
while (!val) {
|
|
ret = ax5043_spi_read_8(conf, &val, AX5043_REG_XTALSTATUS);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
if ((millis() - start) > timeout_ms) {
|
|
return -PQWS_TIMEOUT;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ax5043_spi_read_8(ax5043_conf_t *conf, uint8_t *out, uint16_t reg) {
|
|
int ret = PQWS_SUCCESS;
|
|
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
*out = ax5043ReadReg(reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ax5043_spi_read_16(ax5043_conf_t *conf, uint16_t *out, uint16_t reg) {
|
|
int ret = PQWS_SUCCESS;
|
|
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
*out = ax5043ReadReg2(reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ax5043_spi_read_24(ax5043_conf_t *conf, uint32_t *out, uint16_t reg) {
|
|
int ret = PQWS_SUCCESS;
|
|
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
*out = ax5043ReadReg3(reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ax5043_spi_read_32(ax5043_conf_t *conf, uint32_t *out, uint16_t reg) {
|
|
int ret = PQWS_SUCCESS;
|
|
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
*out = ax5043ReadReg4(reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ax5043_spi_write(ax5043_conf_t *conf, uint16_t reg, const uint8_t *in,
|
|
uint32_t len) {
|
|
int ret = PQWS_SUCCESS;
|
|
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
ax5043WriteRegN(reg, in, len);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ax5043_spi_write_8(ax5043_conf_t *conf, uint16_t reg, uint8_t in) {
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
//printf("Reg\t%04x\t%02x\n", reg, in);
|
|
|
|
ax5043WriteReg(reg, in);
|
|
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
int ax5043_spi_write_16(ax5043_conf_t *conf, uint16_t reg, uint16_t in) {
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
//printf("Reg\t%04x\t%02x\n", reg, (in >> 8)&0xFF);
|
|
//printf("Reg\t%04x\t%02x\n", reg+1, (in >> 0)&0xFF);
|
|
|
|
ax5043WriteReg2(reg, in);
|
|
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
int ax5043_spi_write_24(ax5043_conf_t *conf, uint16_t reg, uint32_t in) {
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
//printf("Reg\t%04x\t%02x\n", reg, (in >> 16)&0xFF);
|
|
//printf("Reg\t%04x\t%02x\n", reg+1, (in >> 8)&0xFF);
|
|
//printf("Reg\t%04x\t%02x\n", reg+2, (in >> 0)&0xFF);
|
|
|
|
ax5043WriteReg3(reg, in);
|
|
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
int ax5043_spi_write_32(ax5043_conf_t *conf, uint16_t reg, uint32_t in) {
|
|
if (!is_ax5043_conf_valid(conf)) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
//printf("Reg\t%04x\t%02x\n", reg, (in >> 24)&0xFF);
|
|
//printf("Reg\t%04x\t%02x\n", reg+1, (in >> 16)&0xFF);
|
|
//printf("Reg\t%04x\t%02x\n", reg+2, (in >> 8)&0xFF);
|
|
//printf("Reg\t%04x\t%02x\n", reg+3, (in >> 0)&0xFF);
|
|
|
|
ax5043WriteReg4(reg, in);
|
|
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Sets properly some undocumented TX registers
|
|
* @param conf the AX5043 configuration handler
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
static inline int set_tx_black_magic_regs(ax5043_conf_t *conf) {
|
|
int ret;
|
|
ret = ax5043_spi_write_8(conf, 0xF00, 0x0F);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ax5043_spi_write_8(conf, 0xF0C, 0x0);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
// Added by Jonathan Brandenburg
|
|
ret = ax5043_spi_write_8(conf, 0xF0D, 0x03);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
// Added by Jonathan Brandenburg
|
|
ret = ax5043_spi_write_8(conf, 0xF10, 0x03);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
// The following line is used for a TCXO
|
|
//ret = ax5043_spi_write_8(conf, 0xF11, 0x0);
|
|
// The following line is used for a crystal
|
|
ret = ax5043_spi_write_8(conf, 0xF11, 0x07);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ax5043_spi_write_8(conf, 0xF1C, 0x07);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ax5043_spi_write_8(conf, 0xF44, 0x24);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Dafuq? Got it from RadioLab */
|
|
ret = ax5043_spi_write_8(conf, 0xF18, 0x06);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Enables/Disables the power amplifier pin
|
|
* @param conf the AX5043 configuration handler
|
|
* @param enable 1 to enable 0 to disable
|
|
* @return 0 on success or appropriate negative error code
|
|
*/
|
|
int ax5043_enable_pwramp(ax5043_conf_t *conf, uint8_t enable) {
|
|
int ret;
|
|
ax5043_set_antsel(conf, enable);
|
|
ret = ax5043_spi_write_8(conf, AX5043_REG_PWRAMP, ~enable & 0x1);
|
|
|
|
if (ret) {
|
|
usleep(PWRAMP_RAMP_PERIOD_US);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int ax5043_set_antsel(ax5043_conf_t *conf, uint8_t val) {
|
|
return ax5043_spi_write_8(conf, AX5043_REG_PINFUNCANTSEL, val & 0x1);
|
|
}
|
|
|
|
/**
|
|
* Wait for the AX5043 to finish transmitting, putting new data in the FIFO as space becomes available
|
|
* @return 0 on success, or appropriate negative error code
|
|
*/
|
|
int ax5043_wait_for_transmit() {
|
|
if (!single_fifo_access) {
|
|
while (__tx_active) {
|
|
static int transmittedPostamble = 0;
|
|
|
|
//usleep(100);
|
|
|
|
int ret;
|
|
uint8_t data_cmd[3] = { AX5043_FIFO_VARIABLE_DATA_CMD, 0, 0 };
|
|
size_t avail;
|
|
size_t chunk_size;
|
|
|
|
if (!__ax5043_conf) {
|
|
return -PQWS_INVALID_PARAM;
|
|
}
|
|
|
|
/* Determine if TX is done */
|
|
uint8_t radiostate = 0;
|
|
ret = ax5043_spi_read_8(__ax5043_conf, &radiostate,
|
|
AX5043_REG_RADIOSTATE);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
radiostate &= 0x0f;
|
|
|
|
if (radiostate == 0) {
|
|
/* tx is done */
|
|
__tx_frame_end(__ax5043_conf);
|
|
transmittedPostamble = 0;
|
|
#ifdef DEBUG_LOGGING
|
|
printf("INFO: TX done\n");
|
|
#endif
|
|
return PQWS_SUCCESS;
|
|
}
|
|
|
|
/* Determine FIFO free space */
|
|
uint16_t fifofree = 0;
|
|
ret = ax5043_spi_read_16(__ax5043_conf, &fifofree,
|
|
AX5043_REG_FIFOFREE1);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* If FIFO has free space fill in data */
|
|
if (fifofree > AX5043_FIFO_FREE_THR && (__tx_remaining || !transmittedPostamble)) {
|
|
|
|
/* Always left some space for the postamble for a simplified logic */
|
|
avail = min_ul(
|
|
AX5043_FIFO_FREE_THR - sizeof(data_cmd) - sizeof(__postamble_cmd),
|
|
__tx_remaining);
|
|
|
|
data_cmd[1] = (uint8_t) (avail + 1);
|
|
chunk_size = sizeof(data_cmd) + avail;
|
|
memcpy(__tx_fifo_chunk + sizeof(data_cmd), __tx_buf + __tx_buf_idx,
|
|
avail);
|
|
|
|
if (avail == __tx_remaining) {
|
|
transmittedPostamble = 1;
|
|
|
|
data_cmd[2] = AX5043_FIFO_PKTEND;
|
|
memcpy(__tx_fifo_chunk + chunk_size, __postamble_cmd,
|
|
sizeof(__postamble_cmd));
|
|
chunk_size += sizeof(__postamble_cmd);
|
|
}
|
|
memcpy(__tx_fifo_chunk, data_cmd, sizeof(data_cmd));
|
|
ax5043_spi_write(__ax5043_conf, AX5043_REG_FIFODATA,
|
|
__tx_fifo_chunk, (uint32_t) chunk_size);
|
|
/* Commit to FIFO ! */
|
|
ret = ax5043_spi_write_8(__ax5043_conf, AX5043_REG_FIFOSTAT,
|
|
AX5043_FIFO_COMMIT_CMD);
|
|
|
|
__tx_remaining -= (uint32_t) avail;
|
|
__tx_buf_idx += avail;
|
|
|
|
}
|
|
}
|
|
} else {
|
|
while (__tx_active) {
|
|
int ret;
|
|
/* Determine if TX is done */
|
|
uint8_t radiostate = 0;
|
|
ret = ax5043_spi_read_8(__ax5043_conf, &radiostate,
|
|
AX5043_REG_RADIOSTATE);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
radiostate &= 0x0f;
|
|
if (radiostate == 0) {
|
|
/* tx is done */
|
|
__tx_active = 0;
|
|
#ifdef DEBUG_LOGGING
|
|
printf("INFO: TX done\n");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
return PQWS_SUCCESS;
|
|
}
|