diff --git a/platform/drivers/baseband/AT1846S_SA8x8.cpp b/platform/drivers/baseband/AT1846S_SA8x8.cpp new file mode 100644 index 00000000..edbacac1 --- /dev/null +++ b/platform/drivers/baseband/AT1846S_SA8x8.cpp @@ -0,0 +1,238 @@ +/*************************************************************************** + * Copyright (C) 2021 - 2023 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * 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 * + **************************************************************************/ + +#include + +#include +#include + +#include +#include "AT1846S.h" + +#define SA8X8_MSG_SIZE 32 + +char rx_buf[SA8X8_MSG_SIZE] = { 0 }; + +void AT1846S::init() +{ + i2c_writeReg16(0x30, 0x0001); // Soft reset + delayMs(50); + + i2c_writeReg16(0x30, 0x0004); // Chip enable + i2c_writeReg16(0x04, 0x0FD0); // 26MHz crystal frequency + i2c_writeReg16(0x1F, 0x1000); // Gpio6 squelch output + i2c_writeReg16(0x09, 0x03AC); + i2c_writeReg16(0x24, 0x0001); + i2c_writeReg16(0x31, 0x0031); + i2c_writeReg16(0x33, 0x45F5); // AGC number + i2c_writeReg16(0x34, 0x2B89); // RX digital gain + i2c_writeReg16(0x3F, 0x3263); // RSSI 3 threshold + i2c_writeReg16(0x41, 0x470F); // Tx digital gain + i2c_writeReg16(0x42, 0x1036); + i2c_writeReg16(0x43, 0x00BB); + i2c_writeReg16(0x44, 0x06FF); // Tx digital gain + i2c_writeReg16(0x47, 0x7F2F); // Soft mute + i2c_writeReg16(0x4E, 0x0082); + i2c_writeReg16(0x4F, 0x2C62); + i2c_writeReg16(0x53, 0x0094); + i2c_writeReg16(0x54, 0x2A3C); + i2c_writeReg16(0x55, 0x0081); + i2c_writeReg16(0x56, 0x0B02); + i2c_writeReg16(0x57, 0x1C00); // Bypass RSSI low-pass + i2c_writeReg16(0x5A, 0x4935); // SQ detection time + i2c_writeReg16(0x58, 0xBCCD); + i2c_writeReg16(0x62, 0x3263); // Modulation detect tresh + i2c_writeReg16(0x4E, 0x2082); + i2c_writeReg16(0x63, 0x16AD); + i2c_writeReg16(0x30, 0x40A4); + delayMs(50); + + i2c_writeReg16(0x30, 0x40A6); // Start calibration + delayMs(100); + i2c_writeReg16(0x30, 0x4006); // Stop calibration + + delayMs(100); + + i2c_writeReg16(0x58, 0xBCED); + i2c_writeReg16(0x0A, 0x7BA0); // PGA gain + i2c_writeReg16(0x41, 0x4731); // Tx digital gain + i2c_writeReg16(0x44, 0x05FF); // Tx digital gain + i2c_writeReg16(0x59, 0x09D2); // Mixer gain + i2c_writeReg16(0x44, 0x05CF); // Tx digital gain + i2c_writeReg16(0x44, 0x05CC); // Tx digital gain + i2c_writeReg16(0x48, 0x1A32); // Noise 1 threshold + i2c_writeReg16(0x60, 0x1A32); // Noise 2 threshold + i2c_writeReg16(0x3F, 0x29D1); // RSSI 3 threshold + i2c_writeReg16(0x0A, 0x7BA0); // PGA gain + i2c_writeReg16(0x49, 0x0C96); // RSSI SQL thresholds + i2c_writeReg16(0x33, 0x45F5); // AGC number + i2c_writeReg16(0x41, 0x470F); // Tx digital gain + i2c_writeReg16(0x42, 0x1036); + i2c_writeReg16(0x43, 0x00BB); +} + +void AT1846S::setBandwidth(const AT1846S_BW band) +{ + if(band == AT1846S_BW::_25) + { + // 25kHz bandwidth + i2c_writeReg16(0x15, 0x1F00); // Tuning bit + i2c_writeReg16(0x32, 0x7564); // AGC target power + i2c_writeReg16(0x3A, 0x44C3); // Modulation detect sel + i2c_writeReg16(0x3F, 0x29D2); // RSSI 3 threshold + i2c_writeReg16(0x3C, 0x0E1C); // Peak detect threshold + i2c_writeReg16(0x48, 0x1E38); // Noise 1 threshold + i2c_writeReg16(0x62, 0x3767); // Modulation detect tresh + i2c_writeReg16(0x65, 0x248A); + i2c_writeReg16(0x66, 0xFF2E); // RSSI comp and AFC range + i2c_writeReg16(0x7F, 0x0001); // Switch to page 1 + i2c_writeReg16(0x06, 0x0024); // AGC gain table + i2c_writeReg16(0x07, 0x0214); + i2c_writeReg16(0x08, 0x0224); + i2c_writeReg16(0x09, 0x0314); + i2c_writeReg16(0x0A, 0x0324); + i2c_writeReg16(0x0B, 0x0344); + i2c_writeReg16(0x0D, 0x1384); + i2c_writeReg16(0x0E, 0x1B84); + i2c_writeReg16(0x0F, 0x3F84); + i2c_writeReg16(0x12, 0xE0EB); + i2c_writeReg16(0x7F, 0x0000); // Back to page 0 + maskSetRegister(0x30, 0x3000, 0x3000); + } + else + { + // 12.5kHz bandwidth + i2c_writeReg16(0x15, 0x1100); // Tuning bit + i2c_writeReg16(0x32, 0x4495); // AGC target power + i2c_writeReg16(0x3A, 0x40C3); // Modulation detect sel + i2c_writeReg16(0x3F, 0x28D0); // RSSI 3 threshold + i2c_writeReg16(0x3C, 0x0F1E); // Peak detect threshold + i2c_writeReg16(0x48, 0x1DB6); // Noise 1 threshold + i2c_writeReg16(0x62, 0x1425); // Modulation detect tresh + i2c_writeReg16(0x65, 0x2494); + i2c_writeReg16(0x66, 0xEB2E); // RSSI comp and AFC range + i2c_writeReg16(0x7F, 0x0001); // Switch to page 1 + i2c_writeReg16(0x06, 0x0014); // AGC gain table + i2c_writeReg16(0x07, 0x020C); + i2c_writeReg16(0x08, 0x0214); + i2c_writeReg16(0x09, 0x030C); + i2c_writeReg16(0x0A, 0x0314); + i2c_writeReg16(0x0B, 0x0324); + i2c_writeReg16(0x0C, 0x0344); + i2c_writeReg16(0x0D, 0x1344); + i2c_writeReg16(0x0E, 0x1B44); + i2c_writeReg16(0x0F, 0x3F44); + i2c_writeReg16(0x12, 0xE0EB); // Back to page 0 + i2c_writeReg16(0x7F, 0x0000); + maskSetRegister(0x30, 0x3000, 0x0000); + } + + reloadConfig(); +} + +void AT1846S::setOpMode(const AT1846S_OpMode mode) +{ + if(mode == AT1846S_OpMode::DMR) + { + // DMR mode + i2c_writeReg16(0x3A, 0x00C2); + i2c_writeReg16(0x33, 0x45F5); + i2c_writeReg16(0x41, 0x4731); + i2c_writeReg16(0x42, 0x1036); + i2c_writeReg16(0x43, 0x00BB); + i2c_writeReg16(0x58, 0xBCFD); // Bit 0 = 1: CTCSS LPF bandwidth to 250Hz + // Bit 3 = 1: bypass CTCSS HPF + // Bit 4 = 1: bypass CTCSS LPF + // Bit 5 = 1: bypass voice LPF + // Bit 6 = 1: bypass voice HPF + // Bit 7 = 1: bypass pre/de-emphasis + // Bit 11 = 1: bypass VOX HPF + // Bit 12 = 1: bypass VOX LPF + // Bit 13 = 1: bypass RSSI LPF + i2c_writeReg16(0x44, 0x06CC); + i2c_writeReg16(0x40, 0x0031); + } + else + { + // FM mode + i2c_writeReg16(0x33, 0x44A5); + i2c_writeReg16(0x41, 0x4431); + i2c_writeReg16(0x42, 0x10F0); + i2c_writeReg16(0x43, 0x00A9); + i2c_writeReg16(0x58, 0xBC05); // Bit 0 = 1: CTCSS LPF badwidth to 250Hz + // Bit 3 = 0: enable CTCSS HPF + // Bit 4 = 0: enable CTCSS LPF + // Bit 5 = 0: enable voice LPF + // Bit 6 = 0: enable voice HPF + // Bit 7 = 0: enable pre/de-emphasis + // Bit 11 = 1: bypass VOX HPF + // Bit 12 = 1: bypass VOX LPF + // Bit 13 = 1: bypass RSSI LPF + i2c_writeReg16(0x44, 0x06FF); + i2c_writeReg16(0x40, 0x0030); + + maskSetRegister(0x57, 0x0001, 0x00); // Audio feedback off + maskSetRegister(0x3A, 0x7000, 0x4000); // Select voice channel + } + + reloadConfig(); +} + +/* + * Implementation of AT1846S I2C interface through SA8x8 + */ + +static constexpr uint8_t devAddr = 0xE2; + +void AT1846S::i2c_init() +{ + // I2C already init'd by platform support package +} + +/* + * These callbacks needs to be implemented by the platform, providing functions + * to read and write from the serial port + */ +extern void radio_uartPrint(const char *fmt, ...); +extern void radio_uartScan(char *buf); + +void AT1846S::i2c_writeReg16(uint8_t reg, uint16_t value) +{ + /* + * SA8x8 with sa8x8_fw uses PEEK and POKE AT commands to write to AT1846S + */ + radio_uartPrint("AT+POKE=%d,%d\r\n", reg, value); + radio_uartScan(rx_buf); + // Check that response is "OK\r" + if (strncmp(rx_buf, "OK\r", 3)) + printk("SA8x8 Error: %d <- %d\n", reg, value); +} + +uint16_t AT1846S::i2c_readReg16(uint8_t reg) +{ + /* + * SA8x8 with sa8x8_fw uses PEEK and POKE AT commands to write to AT1846S + */ + int32_t value = 0; + radio_uartPrint("AT+PEEK=%d\r\n", reg); + radio_uartScan(rx_buf); + sscanf(rx_buf, "%d\r", &value); + return value; +} diff --git a/platform/drivers/baseband/radio_ttwrplus.cpp b/platform/drivers/baseband/radio_ttwrplus.cpp new file mode 100644 index 00000000..3b77a808 --- /dev/null +++ b/platform/drivers/baseband/radio_ttwrplus.cpp @@ -0,0 +1,313 @@ +/*************************************************************************** + * Copyright (C) 2021 - 2023 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * 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 * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "AT1846S.h" +#include "radioUtils.h" + +/* + * Define radio node to control the SA868 + */ +#if DT_NODE_HAS_STATUS(DT_ALIAS(radio), okay) +#define UART_RADIO_DEV_NODE DT_ALIAS(radio) +#else +#error "Please select the correct radio UART device" +#endif + +#define SA8X8_MSG_SIZE 32 + +K_MSGQ_DEFINE(uart_msgq, SA8X8_MSG_SIZE, 10, 4); + +/* receive buffer used in UART ISR callback */ +static char rx_buf[SA8X8_MSG_SIZE]; +static uint16_t rx_buf_pos; + +static const struct device *const radio_dev = DEVICE_DT_GET(UART_RADIO_DEV_NODE); + +#define RADIO_PDN_NODE DT_ALIAS(radio_pdn) + +static const struct gpio_dt_spec radio_pdn = GPIO_DT_SPEC_GET(RADIO_PDN_NODE, gpios); + + +const rtxStatus_t *config; // Pointer to data structure with radio configuration + +Band currRxBand = BND_NONE; // Current band for RX +Band currTxBand = BND_NONE; // Current band for TX + +enum opstatus radioStatus; // Current operating status + +AT1846S& at1846s = AT1846S::instance(); // AT1846S driver + +void radio_serialCb(const struct device *dev, void *user_data) +{ + uint8_t c; + + if (!uart_irq_update(radio_dev)) { + return; + } + + if (!uart_irq_rx_ready(radio_dev)) { + return; + } + + /* read until FIFO empty */ + while (uart_fifo_read(radio_dev, &c, 1) == 1) { + if (c == '\n' && rx_buf_pos > 0) { + /* terminate string */ + rx_buf[rx_buf_pos] = '\0'; + + /* if queue is full, message is silently dropped */ + k_msgq_put(&uart_msgq, &rx_buf, K_NO_WAIT); + + /* reset the buffer (it was copied to the msgq) */ + rx_buf_pos = 0; + } else if (rx_buf_pos < (sizeof(rx_buf) - 1)) { + rx_buf[rx_buf_pos++] = c; + } + /* else: characters beyond buffer size are dropped */ + } +} + +void radio_uartPrint(const char *fmt, ...) +{ + char buf[SA8X8_MSG_SIZE] = { 0 }; + va_list args; + va_start(args, fmt); + vsnprintk(buf, SA8X8_MSG_SIZE, fmt, args); + int msg_len = strnlen(buf, SA8X8_MSG_SIZE); + for (uint16_t i = 0; i < msg_len; i++) { + uart_poll_out(radio_dev, buf[i]); + } + va_end(args); +} + +void radio_uartScan(char *buf) +{ + k_msgq_get(&uart_msgq, buf, K_FOREVER); +} + +char *radio_getFwVersion() +{ + char *tx_buf = (char *) malloc(sizeof(char) * SA8X8_MSG_SIZE); + radio_uartPrint("AT+VERSION\r\n"); + k_msgq_get(&uart_msgq, tx_buf, K_FOREVER); + return tx_buf; +} + +void radio_init(const rtxStatus_t *rtxState) +{ + config = rtxState; + radioStatus = OFF; + int ret; + + // Initialize GPIO for SA868S power down + if (!gpio_is_ready_dt(&radio_pdn)) { + printk("Error: radio device %s is not ready\n", + radio_pdn.port->name); + } + + ret = gpio_pin_configure_dt(&radio_pdn, GPIO_OUTPUT); + if (ret != 0) { + printk("Error %d: failed to configure %s pin %d\n", ret, radio_pdn.port->name, radio_pdn.pin); + } + + if (!device_is_ready(radio_dev)) { + printk("UART device not found!\n"); + return; + } + + ret = uart_irq_callback_user_data_set(radio_dev, radio_serialCb, NULL); + + if (ret < 0) { + if (ret == -ENOTSUP) { + printk("Interrupt-driven UART support not enabled\n"); + } else if (ret == -ENOSYS) { + printk("UART device does not support interrupt-driven API\n"); + } else { + printk("Error setting UART callback: %d\n", ret); + } + + return; + } + + uart_irq_rx_enable(radio_dev); + + ret = gpio_pin_toggle_dt(&radio_pdn); + if (ret != 0) { + printk("Failed to toggle radio power down"); + return; + } + + // Add SA8x8 FW version to Info menu + ui_registerInfoExtraEntry("Radio", radio_getFwVersion); + + // TODO: Implement audio paths configuration + + /* + * Configure AT1846S, keep AF output disabled at power on. + */ + at1846s.init(); +} + +void radio_disableRtx() +{ + at1846s.disableCtcss(); + at1846s.setFuncMode(AT1846S_FuncMode::OFF); + radioStatus = OFF; +} + +void radio_terminate() +{ + radio_disableRtx(); + at1846s.terminate(); +} + +void radio_tuneVcxo(const int16_t vhfOffset, const int16_t uhfOffset) +{ + //TODO: this part will be implemented in the future, when proved to be + // necessary. + (void) vhfOffset; + (void) uhfOffset; +} + +void radio_setOpmode(const enum opmode mode) +{ + switch(mode) + { + case OPMODE_FM: + at1846s.setOpMode(AT1846S_OpMode::FM); // AT1846S in FM mode + break; + + case OPMODE_DMR: + at1846s.setOpMode(AT1846S_OpMode::DMR); + at1846s.setBandwidth(AT1846S_BW::_12P5); + break; + + case OPMODE_M17: + at1846s.setOpMode(AT1846S_OpMode::DMR); // AT1846S in DMR mode, disables RX filter + at1846s.setBandwidth(AT1846S_BW::_25); // Set bandwidth to 25kHz for proper deviation + break; + + default: + break; + } +} + +bool radio_checkRxDigitalSquelch() +{ + return at1846s.rxCtcssDetected(); +} + +void radio_enableAfOutput() +{ + ; +} + +void radio_disableAfOutput() +{ + ; +} + +void radio_enableRx() +{ + if(currRxBand == BND_NONE) return; + + at1846s.setFrequency(config->rxFrequency); + at1846s.setFuncMode(AT1846S_FuncMode::RX); + + if(config->rxToneEn) + { + at1846s.enableRxCtcss(config->rxTone); + } + + radioStatus = RX; +} + +void radio_enableTx() +{ + // TODO; Do not enable Tx until proven to be safe + return; + if(config->txDisable == 1) return; + + at1846s.setFrequency(config->txFrequency); + at1846s.setFuncMode(AT1846S_FuncMode::TX); + + if(config->txToneEn) + { + at1846s.enableTxCtcss(config->txTone); + } + + radioStatus = TX; +} + +void radio_updateConfiguration() +{ + currRxBand = getBandFromFrequency(config->rxFrequency); + currTxBand = getBandFromFrequency(config->txFrequency); + + if((currRxBand == BND_NONE) || (currTxBand == BND_NONE)) return; + + // Set bandwidth, only for analog FM mode + if(config->opMode == OPMODE_FM) + { + switch(config->bandwidth) + { + case BW_12_5: + at1846s.setBandwidth(AT1846S_BW::_12P5); + break; + + case BW_20: + case BW_25: + at1846s.setBandwidth(AT1846S_BW::_25); + break; + + default: + break; + } + } + + /* + * Update VCO frequency and tuning parameters if current operating status + * is different from OFF. + * This is done by calling again the corresponding functions, which is safe + * to do and avoids code duplication. + */ + if(radioStatus == RX) radio_enableRx(); + if(radioStatus == TX) radio_enableTx(); +} + +float radio_getRssi() +{ + return static_cast< float > (at1846s.readRSSI()); +} + +enum opstatus radio_getStatus() +{ + return radioStatus; +} diff --git a/platform/mcu/ESP32S3/zephyr.conf b/platform/mcu/ESP32S3/zephyr.conf index ef4c4719..5bad2a51 100644 --- a/platform/mcu/ESP32S3/zephyr.conf +++ b/platform/mcu/ESP32S3/zephyr.conf @@ -4,6 +4,7 @@ CONFIG_CPP=y CONFIG_REQUIRES_FULL_LIBCPP=y CONFIG_STD_CPP14=y CONFIG_PRINTK=y +CONFIG_PRINTK_SYNC=y CONFIG_LOG=y CONFIG_LOG_PRINTK=y CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/platform/targets/ttwrplus/CMakeLists.txt b/platform/targets/ttwrplus/CMakeLists.txt index b3b1fbf2..f0fc0ece 100644 --- a/platform/targets/ttwrplus/CMakeLists.txt +++ b/platform/targets/ttwrplus/CMakeLists.txt @@ -14,11 +14,12 @@ target_sources(app ${OPENRTX_ROOT}/platform/drivers/display/SH1106_ttwrplus.c ${OPENRTX_ROOT}/platform/drivers/keyboard/keyboard_ttwrplus.c + ${OPENRTX_ROOT}/platform/drivers/baseband/radio_ttwrplus.cpp + ${OPENRTX_ROOT}/platform/drivers/baseband/AT1846S_SA8x8.cpp ${OPENRTX_ROOT}/platform/drivers/stubs/audio_stub.c ${OPENRTX_ROOT}/platform/drivers/stubs/cps_io_stub.c ${OPENRTX_ROOT}/platform/drivers/stubs/nvmem_stub.c - ${OPENRTX_ROOT}/platform/drivers/stubs/radio_stub.c ${OPENRTX_ROOT}/subprojects/XPowersLib/src/XPowersLibInterface.cpp ) diff --git a/platform/targets/ttwrplus/platform.c b/platform/targets/ttwrplus/platform.c index 79838f5a..a53d8e19 100644 --- a/platform/targets/ttwrplus/platform.c +++ b/platform/targets/ttwrplus/platform.c @@ -23,6 +23,7 @@ #include #include +#include #include "pmu.h" #define BUTTON_PTT_NODE DT_NODELABEL(button_ptt) @@ -33,10 +34,12 @@ static const struct device *const qdec_dev = DEVICE_DT_GET(DT_ALIAS(qdec0)); static const hwInfo_t hwInfo = { + .name = "ttwrplus", + .hw_version = 0, + .uhf_band = 1, + .vhf_band = 0, .uhf_maxFreq = 430, .uhf_minFreq = 440, - .uhf_band = 1, - .name = "ttwrplus" }; void platform_init() diff --git a/platform/targets/ttwrplus/ttwrplus.dts b/platform/targets/ttwrplus/ttwrplus.dts index d1f1faf9..8c07cfb4 100644 --- a/platform/targets/ttwrplus/ttwrplus.dts +++ b/platform/targets/ttwrplus/ttwrplus.dts @@ -18,6 +18,9 @@ i2c-0 = &i2c0; watchdog0 = &wdt0; radio = &uart0; + radio-pwr = &radio_pwr; + radio-pdn = &radio_pdn; + radio-ptt = &radio_ptt; qdec0 = &pcnt; }; @@ -29,6 +32,25 @@ zephyr,display = &ssd1306; }; + leds: leds { + compatible = "gpio-leds"; + + radio_pwr: radio_pwr { + gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>; + label = "SA868S H/L on IO38"; + }; + + radio_pdn: radio_pdn { + gpios = <&gpio1 8 GPIO_ACTIVE_LOW>; + label = "SA868S PDN on IO40"; + }; + + radio_ptt: radio_ptt { + gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>; + label = "SA868S PTT on IO41"; + }; + }; + buttons: buttons { compatible = "zephyr,gpio-keys"; diff --git a/platform/targets/ttwrplus/ttwrplus_defconfig b/platform/targets/ttwrplus/ttwrplus_defconfig index cdcb1c52..6825fa34 100644 --- a/platform/targets/ttwrplus/ttwrplus_defconfig +++ b/platform/targets/ttwrplus/ttwrplus_defconfig @@ -10,5 +10,9 @@ CONFIG_DYNAMIC_THREAD_PREFER_POOL=y CONFIG_DYNAMIC_THREAD_POOL_SIZE=4 CONFIG_COMMON_LIBC_MALLOC=y CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=131072 +CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=0 CONFIG_INPUT=y CONFIG_SENSOR=y +CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_ASSERT=y