MMDVM/MMDVMCal/MMDVMCal.cpp
2022-08-24 08:10:10 +02:00

1473 lines
35 KiB
C++
Executable File

/*
* Copyright (C) 2015,2016,2017,2018,2020 by Jonathan Naylor G4KLX
* Copyright (C) 2017,2018 by Andy Uribe CA6JAU
* Copyright (C) 2018 by Bryan Biedenkapp N2PLL
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "MMDVMCal.h"
#include "Version.h"
#include <cstdio>
#if defined(_WIN32) || defined(_WIN64)
#define EOL "\n"
#else
#include <unistd.h>
#define EOL "\r\n"
#endif
#include "Utils.h"
const unsigned char MMDVM_GET_STATUS = 0x01U;
const unsigned char MMDVM_FRAME_START = 0xE0U;
const unsigned char MMDVM_GET_VERSION = 0x00U;
const unsigned char MMDVM_SET_CONFIG = 0x02U;
const unsigned char MMDVM_SET_FREQ = 0x04U;
const unsigned char MMDVM_CAL_DATA = 0x08U;
const unsigned char MMDVM_ACK = 0x70U;
const unsigned char MMDVM_NAK = 0x7FU;
const unsigned int MAX_RESPONSES = 30U;
const unsigned int BUFFER_LENGTH = 2000U;
int main(int argc, char** argv)
{
if (argc < 2) {
::fprintf(stderr, "Usage: MMDVMCal <port>\n");
return 1;
}
CMMDVMCal cal(argv[1]);
return cal.run();
}
CMMDVMCal::CMMDVMCal(const std::string& port) :
m_serial(port, SERIAL_115200),
m_console(),
m_transmit(false),
m_carrier(false),
m_txLevel(50.0F),
m_rxLevel(50.0F),
m_txDCOffset(0),
m_rxDCOffset(0),
m_txInvert(false),
m_rxInvert(false),
m_pttInvert(false),
m_frequency(433000000U),
m_startfrequency(433000000U),
m_step(50U),
m_power(100.0F),
m_mode(STATE_DSTARCAL),
m_duplex(true),
m_debug(false),
m_buffer(NULL),
m_length(0U),
m_offset(0U),
m_dstarEnabled(false),
m_dmrEnabled(false),
m_dmrBERFEC(true),
m_ysfEnabled(false),
m_p25Enabled(false),
m_nxdnEnabled(false),
m_pocsagEnabled(false),
m_fmEnabled(false)
{
m_buffer = new unsigned char[BUFFER_LENGTH];
}
CMMDVMCal::~CMMDVMCal()
{
}
int CMMDVMCal::run()
{
bool ret = m_serial.open();
if (!ret)
return 1;
ret = initModem();
if (!ret) {
m_serial.close();
return 1;
}
ret = m_console.open();
if (!ret) {
m_serial.close();
return 1;
}
if (m_hwType == HWT_MMDVM)
loop_MMDVM();
else if (m_hwType == HWT_MMDVM_HS)
loop_MMDVM_HS();
if (m_transmit)
setTransmit();
m_serial.close();
m_console.close();
if (m_hwType == HWT_MMDVM) {
::fprintf(stdout, "PTT Invert: %s, RX Invert: %s, TX Invert: %s, RX Level: %.1f%%, TX Level: %.1f%%, TX DC Offset: %d, RX DC Offset: %d" EOL,
m_pttInvert ? "yes" : "no", m_rxInvert ? "yes" : "no", m_txInvert ? "yes" : "no",
m_rxLevel, m_txLevel, m_txDCOffset, m_rxDCOffset);
}
else if (m_hwType == HWT_MMDVM_HS) {
::fprintf(stdout, "TX Level: %.1f%%, Frequency Offset: %d, RF Level: %.1f%%" EOL, m_txLevel, (int)(m_frequency - m_startfrequency), m_power);
}
return 0;
}
void CMMDVMCal::loop_MMDVM()
{
displayHelp_MMDVM();
unsigned int counter=0;
bool end = false;
while (!end) {
int c = m_console.getChar();
switch (c) {
case 'H':
case 'h':
displayHelp_MMDVM();
break;
case 'W':
case 'w':
setDebug();
break;
case 'T':
setTXLevel(1);
break;
case 't':
setTXLevel(-1);
break;
case 'R':
setRXLevel(1);
break;
case 'r':
setRXLevel(-1);
break;
case ' ':
setTransmit();
break;
case 'I':
setTXInvert();
break;
case 'i':
setRXInvert();
break;
case 'P':
case 'p':
setPTTInvert();
break;
case 'o':
setTXDCOffset(-1);
break;
case 'O':
setTXDCOffset(1);
break;
case 'c':
setRXDCOffset(-1);
break;
case 'C':
setRXDCOffset(1);
break;
case 'Q':
case 'q':
end = true;
break;
case 'V':
case 'v':
::fprintf(stdout, VERSION EOL);
break;
case 'F':
setFMDeviation();
break;
case 'D':
setDMRDeviation();
break;
case 'd':
setDSTAR();
break;
case 'L':
case 'l':
setLowFrequencyCal();
break;
case 'A':
setDMRCal1K();
break;
case 'M':
case 'm':
setDMRDMO1K();
break;
case 'K':
case 'k':
setDSTARBER_FEC();
break;
case 'b':
setDMRBER_FEC();
break;
case 'B':
setDMRBER_1K();
break;
case 'J':
setYSFBER_FEC();
break;
case 'j':
setP25BER_FEC();
break;
case 'n':
setNXDNBER_FEC();
break;
case 'a':
setP25Cal1K();
break;
case 'N':
setNXDNCal1K();
break;
case 'g':
setPOCSAGCal();
break;
case 'S':
case 's':
setRSSI();
break;
case -1:
case 0:
break;
default:
::fprintf(stderr, "Unknown command - %c (H/h for help)" EOL, c);
break;
}
RESP_TYPE_MMDVM resp = getResponse();
if (resp == RTM_OK)
displayModem(m_buffer, m_length);
m_ber.clock();
sleep(5U);
if(counter >= 200)
{
if (getStatus())
displayModem(m_buffer, m_length);
counter=0;
}
counter++;
}
}
void CMMDVMCal::displayHelp_MMDVM()
{
::fprintf(stdout, "The commands are:" EOL);
::fprintf(stdout, " H/h Display help" EOL);
::fprintf(stdout, " Q/q Quit" EOL);
::fprintf(stdout, " W/w Enable/disable modem debug messages" EOL);
::fprintf(stdout, " I Toggle transmit inversion" EOL);
::fprintf(stdout, " i Toggle receive inversion" EOL);
::fprintf(stdout, " O Increase TX DC offset level" EOL);
::fprintf(stdout, " o Decrease TX DC offset level" EOL);
::fprintf(stdout, " C Increase RX DC offset level" EOL);
::fprintf(stdout, " c Decrease RX DC offset level" EOL);
::fprintf(stdout, " P/p Toggle PTT inversion" EOL);
::fprintf(stdout, " R Increase receive level" EOL);
::fprintf(stdout, " r Decrease receive level" EOL);
::fprintf(stdout, " T Increase transmit level" EOL);
::fprintf(stdout, " t Decrease transmit level" EOL);
::fprintf(stdout, " d D-Star Mode" EOL);
::fprintf(stdout, " F FM Deviation Mode (Adjust for correct Deviation)" EOL);
::fprintf(stdout, " D DMR Deviation Mode (Adjust for 2.75Khz Deviation)" EOL);
::fprintf(stdout, " L/l DMR Low Frequency Mode (80 Hz square wave)" EOL);
::fprintf(stdout, " A DMR Duplex 1031 Hz Test Pattern (TS2 CC1 ID1 TG9)" EOL);
::fprintf(stdout, " M/m DMR Simplex 1031 Hz Test Pattern (CC1 ID1 TG9)" EOL);
::fprintf(stdout, " a P25 1011 Hz Test Pattern (NAC293 ID1 TG1)" EOL);
::fprintf(stdout, " N NXDN 1031 Hz Test Pattern (RAN1 ID1 TG1)" EOL);
::fprintf(stdout, " K/k BER Test Mode (FEC) for D-Star" EOL);
::fprintf(stdout, " b BER Test Mode (FEC) for DMR Simplex (CC1)" EOL);
::fprintf(stdout, " B BER Test Mode (1031 Hz Test Pattern) for DMR Simplex (CC1 ID1 TG9)" EOL);
::fprintf(stdout, " J BER Test Mode (FEC) for YSF" EOL);
::fprintf(stdout, " j BER Test Mode (FEC) for P25" EOL);
::fprintf(stdout, " n BER Test Mode (FEC) for NXDN" EOL);
::fprintf(stdout, " g POCSAG 600Hz Test Pattern" EOL);
::fprintf(stdout, " S/s RSSI Mode" EOL);
::fprintf(stdout, " V/v Display version of MMDVMCal" EOL);
::fprintf(stdout, " <space> Toggle transmit" EOL);
}
void CMMDVMCal::loop_MMDVM_HS()
{
m_mode = STATE_DMRCAL;
unsigned int counter=0;
setFrequency();
writeConfig(m_txLevel, m_debug);
displayHelp_MMDVM_HS();
bool end = false;
while (!end) {
int c = m_console.getChar();
switch (c) {
case 'H':
case 'h':
displayHelp_MMDVM_HS();
break;
case 'W':
case 'w':
setDebug();
break;
case 'C':
case 'c':
setCarrier();
break;
case 'E':
case 'e':
setEnterFreq();
break;
case 'T':
setTXLevel(1);
break;
case 't':
setTXLevel(-1);
break;
case 'F':
setFreq(1);
break;
case 'f':
setFreq(-1);
break;
case 'Z':
case 'z':
setStepFreq();
break;
case 'P':
setPower(1);
break;
case 'p':
setPower(-1);
break;
case ' ':
setTransmit();
break;
case 'Q':
case 'q':
end = true;
break;
case 'V':
case 'v':
::fprintf(stdout, VERSION EOL);
break;
case 'D':
case 'd':
setDMRDeviation();
break;
case 'M':
case 'm':
setDMRDMO1K();
break;
case 'K':
case 'k':
setDSTARBER_FEC();
break;
case 'b':
setDMRBER_FEC();
break;
case 'B':
setDMRBER_1K();
break;
case 'J':
setYSFBER_FEC();
break;
case 'j':
setP25BER_FEC();
break;
case 'n':
setNXDNBER_FEC();
break;
case 'g':
setPOCSAGCal();
break;
case 'S':
case 's':
setRSSI();
break;
case 'I':
case 'i':
setIntCal();
break;
case -1:
case 0:
break;
default:
::fprintf(stderr, "Unknown command - %c (H/h for help)" EOL, c);
break;
}
RESP_TYPE_MMDVM resp = getResponse();
if (resp == RTM_OK)
displayModem(m_buffer, m_length);
m_ber.clock();
sleep(5U);
if(counter >= 200)
{
if (getStatus())
displayModem(m_buffer, m_length);
counter=0;
}
counter++;
}
}
void CMMDVMCal::displayHelp_MMDVM_HS()
{
::fprintf(stdout, "The commands are:" EOL);
::fprintf(stdout, " H/h Display help" EOL);
::fprintf(stdout, " Q/q Quit" EOL);
::fprintf(stdout, " W/w Enable/disable modem debug messages" EOL);
::fprintf(stdout, " E/e Enter frequency (current: %u Hz)" EOL, m_frequency);
::fprintf(stdout, " F Increase frequency" EOL);
::fprintf(stdout, " f Decrease frequency" EOL);
::fprintf(stdout, " Z/z Enter frequency step" EOL);
::fprintf(stdout, " T Increase deviation" EOL);
::fprintf(stdout, " t Decrease deviation" EOL);
::fprintf(stdout, " P Increase RF power" EOL);
::fprintf(stdout, " p Decrease RF power" EOL);
::fprintf(stdout, " C/c Carrier Only Mode" EOL);
::fprintf(stdout, " K/k Set FM Deviation Modes" EOL);
::fprintf(stdout, " D/d DMR Deviation Mode (Adjust for 2.75Khz Deviation)" EOL);
::fprintf(stdout, " M/m DMR Simplex 1031 Hz Test Pattern (CC1 ID1 TG9)" EOL);
::fprintf(stdout, " K/k BER Test Mode (FEC) for D-Star" EOL);
::fprintf(stdout, " b BER Test Mode (FEC) for DMR Simplex (CC1)" EOL);
::fprintf(stdout, " B BER Test Mode (1031 Hz Test Pattern) for DMR Simplex (CC1 ID1 TG9)" EOL);
::fprintf(stdout, " J BER Test Mode (FEC) for YSF" EOL);
::fprintf(stdout, " j BER Test Mode (FEC) for P25" EOL);
::fprintf(stdout, " n BER Test Mode (FEC) for NXDN" EOL);
::fprintf(stdout, " g POCSAG 600Hz Test Pattern" EOL);
::fprintf(stdout, " S/s RSSI Mode" EOL);
::fprintf(stdout, " I/i Interrupt Counter Mode" EOL);
::fprintf(stdout, " V/v Display version of MMDVMCal" EOL);
::fprintf(stdout, " <space> Toggle transmit" EOL);
}
bool CMMDVMCal::initModem()
{
sleep(2000U); // 2s
for (unsigned int i = 0U; i < 6U; i++) {
unsigned char buffer[3U];
buffer[0U] = MMDVM_FRAME_START;
buffer[1U] = 3U;
buffer[2U] = MMDVM_GET_VERSION;
int ret = m_serial.write(buffer, 3U);
if (ret != 3)
return false;
#if defined(__APPLE__)
m_serial.setNonblock(true);
#endif
for (unsigned int count = 0U; count < MAX_RESPONSES; count++) {
sleep(10U);
RESP_TYPE_MMDVM resp = getResponse();
if (resp == RTM_OK && m_buffer[2U] == MMDVM_GET_VERSION) {
::fprintf(stderr, "Version: %u, description: %.*s" EOL, m_buffer[3U], m_length - 4U, m_buffer + 4U);
if (::memcmp(m_buffer + 4U, "MMDVM ", 6U) == 0)
m_hwType = HWT_MMDVM;
else if ((::memcmp(m_buffer + 4U, "ZUMspot", 7U) == 0) || (::memcmp(m_buffer + 4U, "MMDVM_HS_Hat", 12U) == 0) || (::memcmp(m_buffer + 4U, "MMDVM_HS_Dual_Hat", 17U) == 0) || (::memcmp(m_buffer + 4U, "Nano_hotSPOT", 12U) == 0) || (::memcmp(m_buffer + 4U, "Nano_DV", 7U) == 0) || (::memcmp(m_buffer + 4U, "MMDVM_HS-", 9U) == 0) || (::memcmp(m_buffer + 4U, "D2RG_MMDVM_HS", 13U) == 0))
m_hwType = HWT_MMDVM_HS;
else {
::fprintf(stderr, "Board not supported" EOL);
return false;
}
return writeConfig(m_txLevel, m_debug);
return true;
}
}
sleep(1500U);
}
::fprintf(stderr, "Unable to read the firmware version after six attempts" EOL);
return false;
}
bool CMMDVMCal::writeConfig(float txlevel, bool debug)
{
unsigned char buffer[50U];
buffer[0U] = MMDVM_FRAME_START;
buffer[1U] = 24U;
buffer[2U] = MMDVM_SET_CONFIG;
buffer[3U] = 0x00U;
if (m_rxInvert)
buffer[3U] |= 0x01U;
if (m_txInvert)
buffer[3U] |= 0x02U;
if (m_pttInvert)
buffer[3U] |= 0x04U;
if (!m_duplex)
buffer[3U] |= 0x80U;
if (debug)
buffer[3U] |= 0x10U;
buffer[4U] = 0x00U;
if (m_dstarEnabled)
buffer[4U] |= 0x01U;
if (m_dmrEnabled)
buffer[4U] |= 0x02U;
if (m_ysfEnabled)
buffer[4U] |= 0x04U;
if (m_p25Enabled)
buffer[4U] |= 0x08U;
if (m_nxdnEnabled)
buffer[4U] |= 0x10U;
if (m_pocsagEnabled)
buffer[4U] |= 0x20U;
if (m_fmEnabled)
buffer[4U] |= 0x40U;
buffer[5U] = 0U;
buffer[6U] = m_mode;
buffer[7U] = (unsigned char)(m_rxLevel * 2.55F + 0.5F);
buffer[8U] = (unsigned char)(txlevel * 2.55F + 0.5F);
buffer[9U] = 1U;
buffer[10U] = 0U;
buffer[11U] = 128U;
buffer[12U] = (unsigned char)(txlevel * 2.55F + 0.5F);
buffer[13U] = (unsigned char)(txlevel * 2.55F + 0.5F);
buffer[14U] = (unsigned char)(txlevel * 2.55F + 0.5F);
buffer[15U] = (unsigned char)(txlevel * 2.55F + 0.5F);
buffer[16U] = (unsigned char)(m_txDCOffset + 128);
buffer[17U] = (unsigned char)(m_rxDCOffset + 128);
buffer[18U] = (unsigned char)(txlevel * 2.55F + 0.5F);
buffer[19U] = 0U;
buffer[20U] = (unsigned char)(txlevel * 2.55F + 0.5F);
buffer[21U] = (unsigned char)(txlevel * 2.55F + 0.5F);
buffer[22U] = 0U;
buffer[23U] = 0U;
int ret = m_serial.write(buffer, 24U);
if (ret <= 0)
return false;
unsigned int count = 0U;
RESP_TYPE_MMDVM resp;
do {
sleep(10U);
resp = getResponse();
if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) {
count++;
if (count >= MAX_RESPONSES) {
::fprintf(stdout, "The MMDVM is not responding to the SET_CONFIG command" EOL);
return false;
}
}
} while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK);
if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) {
::fprintf(stdout, "Received a NAK to the SET_CONFIG command from the modem: %u" EOL, m_buffer[4U]);
return false;
}
return true;
}
bool CMMDVMCal::setRXInvert()
{
m_rxInvert = !m_rxInvert;
::fprintf(stdout, "RX Invert: %s" EOL, m_rxInvert ? "On" : "Off");
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setTXInvert()
{
m_txInvert = !m_txInvert;
::fprintf(stdout, "TX Invert: %s" EOL, m_txInvert ? "On" : "Off");
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setPTTInvert()
{
m_pttInvert = !m_pttInvert;
::fprintf(stdout, "PTT Invert: %s" EOL, m_pttInvert ? "On" : "Off");
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setDebug()
{
m_debug = !m_debug;
::fprintf(stdout, "Modem debug: %s" EOL, m_debug ? "On" : "Off");
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setDMRDeviation()
{
m_mode = STATE_DMRCAL;
m_carrier = false;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "DMR Deviation Mode (Set to 2.75Khz Deviation)" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setFMDeviation()
{
switch (m_mode)
{
case STATE_FMCAL10K:
m_mode = STATE_FMCAL12K;
::fprintf(stdout, "FM 12.5Khz channel spacing with 2.50Khz Deviation (1039.5hz)" EOL);
break;
case STATE_FMCAL12K:
m_mode = STATE_FMCAL15K;
::fprintf(stdout, "FM 15Khz channel spacing with 3.0Khz Deviation (1247hz)" EOL);
break;
case STATE_FMCAL15K:
m_mode = STATE_FMCAL20K;
::fprintf(stdout, "FM 20Khz channel spacing with 4.0Khz Deviation (1663hz)" EOL);
break;
case STATE_FMCAL20K:
m_mode = STATE_FMCAL25K;
::fprintf(stdout, "FM 25Khz channel spacing with 5.0Khz Deviation (2079hz)" EOL);
break;
case STATE_FMCAL25K:
m_mode = STATE_FMCAL30K;
::fprintf(stdout, "FM 30Khz channel spacing with 6.0Khz Deviation (2495hz)" EOL);
break;
default:
m_mode = STATE_FMCAL10K;
::fprintf(stdout, "FM 10Khz channel spacing with 2.30Khz Deviation (956hz)" EOL);
break;
}
m_carrier = false;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setLowFrequencyCal()
{
m_mode = STATE_LFCAL;
m_carrier = false;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "DMR Low Frequency Mode (80 Hz square wave)" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setDMRCal1K()
{
m_mode = STATE_DMRCAL1K;
m_carrier = false;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "DMR Duplex 1031 Hz Test Pattern (TS2 CC1 ID1 TG9)" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setDMRDMO1K()
{
if (m_transmit && (m_hwType == HWT_MMDVM_HS)) {
::fprintf(stdout, "First turn off the transmitter" EOL);
return false;
}
else {
m_mode = STATE_DMRDMO1K;
m_carrier = false;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "DMR Simplex 1031 Hz Test Pattern (CC1 ID1 TG9)" EOL);
return writeConfig(m_txLevel, m_debug);
}
}
bool CMMDVMCal::setP25Cal1K()
{
m_mode = STATE_P25CAL1K;
m_carrier = false;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "P25 1011 Hz Test Pattern (NAC293 ID1 TG1)" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setNXDNCal1K()
{
m_mode = STATE_NXDNCAL1K;
m_carrier = false;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "NXDN 1031 Hz Test Pattern (RAN1 ID1 TG1)" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setPOCSAGCal()
{
m_mode = STATE_POCSAGCAL;
m_carrier = false;
m_duplex = false;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "POCSAG 600 Hz Test Pattern" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setDSTARBER_FEC()
{
m_mode = STATE_DSTAR;
m_carrier = false;
m_duplex = false;
m_dstarEnabled = true;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "BER Test Mode (FEC) for D-Star" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setDMRBER_FEC()
{
m_mode = STATE_DMR;
m_carrier = false;
m_duplex = false;
m_dstarEnabled = false;
m_dmrEnabled = true;
m_dmrBERFEC = true;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "BER Test Mode (FEC) for DMR Simplex" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setDMRBER_1K()
{
m_mode = STATE_DMR;
m_carrier = false;
m_duplex = false;
m_dstarEnabled = false;
m_dmrEnabled = true;
m_dmrBERFEC = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "BER Test Mode (1031 Hz Test Pattern) for DMR Simplex" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setYSFBER_FEC()
{
m_mode = STATE_YSF;
m_carrier = false;
m_duplex = false;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = true;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "BER Test Mode (FEC) for YSF" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setP25BER_FEC()
{
m_mode = STATE_P25;
m_carrier = false;
m_duplex = false;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = true;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "BER Test Mode (FEC) for P25" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setNXDNBER_FEC()
{
m_mode = STATE_NXDN;
m_carrier = false;
m_duplex = false;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = true;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "BER Test Mode (FEC) for NXDN" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setDSTAR()
{
m_mode = STATE_DSTARCAL;
m_carrier = false;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "D-Star Mode" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setRSSI()
{
m_mode = STATE_RSSICAL;
m_carrier = false;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "RSSI Mode" EOL);
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setIntCal()
{
m_mode = STATE_INTCAL;
m_carrier = false;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "Interrupt Counter Mode" EOL);
return writeConfig(m_txLevel, true);
}
bool CMMDVMCal::setCarrier()
{
m_mode = STATE_DMRCAL;
m_carrier = true;
m_duplex = true;
m_dstarEnabled = false;
m_dmrEnabled = false;
m_ysfEnabled = false;
m_p25Enabled = false;
m_nxdnEnabled = false;
m_pocsagEnabled = false;
m_fmEnabled = false;
::fprintf(stdout, "Carrier Only Mode: %u Hz" EOL, m_frequency);
return writeConfig(0.0F, m_debug);
}
bool CMMDVMCal::setEnterFreq()
{
char buff[256U];
::fprintf(stdout, "Enter frequency (current %u Hz):" EOL, m_frequency);
m_console.close();
if (std::fgets(buff, 256, stdin) != NULL ) {
unsigned long int freq = std::strtoul(buff, NULL, 10);
if (freq >= 100000000U && freq <= 999999999U) {
m_frequency = (unsigned int)freq;
m_startfrequency = m_frequency;
::fprintf(stdout, "New frequency: %u Hz" EOL, m_frequency);
setFrequency();
}
else
::fprintf(stdout, "Not valid frequency" EOL);
}
m_console.open();
displayHelp_MMDVM_HS();
return writeConfig(m_txLevel, m_debug);
}
bool CMMDVMCal::setRXLevel(int incr)
{
if (incr > 0 && m_rxLevel < 100.0F) {
m_rxLevel += 0.5F;
::fprintf(stdout, "RX Level: %.1f%%" EOL, m_rxLevel);
return writeConfig(m_txLevel, m_debug);
}
if (incr < 0 && m_rxLevel > 0.0F) {
m_rxLevel -= 0.5F;
::fprintf(stdout, "RX Level: %.1f%%" EOL, m_rxLevel);
return writeConfig(m_txLevel, m_debug);
}
return true;
}
bool CMMDVMCal::setTXLevel(int incr)
{
if (!m_carrier) {
if (incr > 0 && m_txLevel < 100.0F) {
m_txLevel += 0.5F;
::fprintf(stdout, "TX Level: %.1f%%" EOL, m_txLevel);
return writeConfig(m_txLevel, m_debug);
}
if (incr < 0 && m_txLevel > 0.0F) {
m_txLevel -= 0.5F;
::fprintf(stdout, "TX Level: %.1f%%" EOL, m_txLevel);
return writeConfig(m_txLevel, m_debug);
}
}
return true;
}
bool CMMDVMCal::setFreq(int incr)
{
bool ret;
if (incr > 0) {
m_frequency += m_step;
::fprintf(stdout, "TX frequency: %u" EOL, m_frequency);
setFrequency();
if (m_carrier)
ret = writeConfig(0.0F, m_debug);
else
ret = writeConfig(m_txLevel, m_debug);
return ret;
}
if (incr < 0) {
m_frequency -= m_step;
::fprintf(stdout, "TX frequency: %u" EOL, m_frequency);
setFrequency();
if (m_carrier)
ret = writeConfig(0.0F, m_debug);
else
ret = writeConfig(m_txLevel, m_debug);
return ret;
}
return true;
}
bool CMMDVMCal::setStepFreq()
{
char buff[256U];
::fprintf(stdout, "Enter frequency step (current %u Hz):" EOL, m_step);
m_console.close();
if (std::fgets(buff, 256, stdin) != NULL ) {
unsigned long int freq = std::strtoul(buff, NULL, 10);
if (freq >= 10U && freq <= 25000U) {
m_step = (unsigned int)freq;
::fprintf(stdout, "New frequency step: %u Hz" EOL, m_step);
}
else
::fprintf(stdout, "Not valid frequency step" EOL);
}
m_console.open();
displayHelp_MMDVM_HS();
return true;
}
bool CMMDVMCal::setPower(int incr)
{
if (incr > 0 && m_power < 100.0F) {
m_power += 1.0F;
::fprintf(stdout, "RF power: %.1f%%" EOL, m_power);
setFrequency();
return writeConfig(m_txLevel, m_debug);
}
if (incr < 0 && m_power > 0.0F) {
m_power -= 1.0F;
::fprintf(stdout, "RF power: %.1f%%" EOL, m_power);
setFrequency();
return writeConfig(m_txLevel, m_debug);
}
return true;
}
bool CMMDVMCal::setTXDCOffset(int incr)
{
if (incr > 0 && m_txDCOffset < 127) {
m_txDCOffset++;
::fprintf(stdout, "TX DC Offset: %d" EOL, m_txDCOffset);
return writeConfig(m_txLevel, m_debug);
}
if (incr < 0 && m_txDCOffset > -128) {
m_txDCOffset--;
::fprintf(stdout, "TX DC Offset: %d" EOL, m_txDCOffset);
return writeConfig(m_txLevel, m_debug);
}
return true;
}
bool CMMDVMCal::setRXDCOffset(int incr)
{
if (incr > 0 && m_rxDCOffset < 127) {
m_rxDCOffset++;
::fprintf(stdout, "RX DC Offset: %d" EOL, m_rxDCOffset);
return writeConfig(m_txLevel, m_debug);
}
if (incr < 0 && m_rxDCOffset > -128) {
m_rxDCOffset--;
::fprintf(stdout, "RX DC Offset: %d" EOL, m_rxDCOffset);
return writeConfig(m_txLevel, m_debug);
}
return true;
}
bool CMMDVMCal::setTransmit()
{
m_transmit = !m_transmit;
unsigned char buffer[50U];
buffer[0U] = MMDVM_FRAME_START;
buffer[1U] = 4U;
buffer[2U] = MMDVM_CAL_DATA;
buffer[3U] = m_transmit ? 0x01U : 0x00U;
int ret = m_serial.write(buffer, 4U);
if (ret <= 0)
return false;
unsigned int count = 0U;
RESP_TYPE_MMDVM resp;
do {
sleep(10U);
resp = getResponse();
if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) {
count++;
if (count >= MAX_RESPONSES) {
::fprintf(stdout, "The MMDVM is not responding to the CAL_DATA command" EOL);
return false;
}
}
} while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK);
if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) {
::fprintf(stdout, "Received a NAK to the CAL_DATA command from the modem: %u" EOL, m_buffer[4U]);
return false;
}
if (m_transmit)
::fprintf(stdout, "Set transmitter ON" EOL);
else
::fprintf(stdout, "Set transmitter OFF" EOL);
return true;
}
void CMMDVMCal::displayModem(const unsigned char *buffer, unsigned int length)
{
if (buffer[2U] == MMDVM_GET_STATUS) {
bool adcOverflow = (buffer[5U] & 0x02U) == 0x02U;
if (adcOverflow)
::fprintf(stderr, "MMDVM ADC levels have overflowed" EOL);
bool rxOverflow = (buffer[5U] & 0x04U) == 0x04U;
if (rxOverflow)
::fprintf(stderr,"MMDVM RX buffer has overflowed" EOL);
bool txOverflow = (buffer[5U] & 0x08U) == 0x08U;
if (txOverflow)
::fprintf(stderr,"MMDVM TX buffer has overflowed" EOL);
bool dacOverflow = (buffer[5U] & 0x20U) == 0x20U;
if (dacOverflow)
::fprintf(stderr,"MMDVM DAC levels have overflowed" EOL);
} else if (buffer[2U] == 0x08U) {
bool inverted = (buffer[3U] == 0x80U);
short high = buffer[4U] << 8 | buffer[5U];
short low = buffer[6U] << 8 | buffer[7U];
short diff = high - low;
short centre = (high + low) / 2;
::fprintf(stdout, "Levels: inverted: %s, max: %d, min: %d, diff: %d, centre: %d" EOL, inverted ? "yes" : "no", high, low, diff, centre);
} else if (buffer[2U] == 0x09U) {
unsigned short max = buffer[3U] << 8 | buffer[4U];
unsigned short min = buffer[5U] << 8 | buffer[6U];
unsigned short ave = buffer[7U] << 8 | buffer[8U];
::fprintf(stdout, "RSSI: max: %u, min: %u, ave: %u" EOL, max, min, ave);
} else if (buffer[2U] == 0xF1U) {
::fprintf(stdout, "Debug: %.*s" EOL, length - 3U, buffer + 3U);
} else if (buffer[2U] == 0xF2U) {
short val1 = (buffer[length - 2U] << 8) | buffer[length - 1U];
::fprintf(stdout, "Debug: %.*s %d" EOL, length - 5U, buffer + 3U, val1);
} else if (buffer[2U] == 0xF3U) {
short val1 = (buffer[length - 4U] << 8) | buffer[length - 3U];
short val2 = (buffer[length - 2U] << 8) | buffer[length - 1U];
::fprintf(stdout, "Debug: %.*s %d %d" EOL, length - 7U, buffer + 3U, val1, val2);
} else if (buffer[2U] == 0xF4U) {
short val1 = (buffer[length - 6U] << 8) | buffer[length - 5U];
short val2 = (buffer[length - 4U] << 8) | buffer[length - 3U];
short val3 = (buffer[length - 2U] << 8) | buffer[length - 1U];
::fprintf(stdout, "Debug: %.*s %d %d %d" EOL, length - 9U, buffer + 3U, val1, val2, val3);
} else if (buffer[2U] == 0xF5U) {
short val1 = (buffer[length - 8U] << 8) | buffer[length - 7U];
short val2 = (buffer[length - 6U] << 8) | buffer[length - 5U];
short val3 = (buffer[length - 4U] << 8) | buffer[length - 3U];
short val4 = (buffer[length - 2U] << 8) | buffer[length - 1U];
::fprintf(stdout, "Debug: %.*s %d %d %d %d" EOL, length - 11U, buffer + 3U, val1, val2, val3, val4);
} else if (buffer[2U] == 0x10U || buffer[2U] == 0x11U || buffer[2U] == 0x12U || buffer[2U] == 0x13U) {
m_ber.DSTARFEC(buffer + 3U, buffer[2U]);
} else if (buffer[2U] == 0x18U || buffer[2U] == 0x1AU) {
if (m_dmrBERFEC)
m_ber.DMRFEC(buffer + 4U, buffer[3]);
else
m_ber.DMR1K(buffer + 4U, buffer[3]);
} else if (buffer[2U] == 0x20U) {
m_ber.YSFFEC(buffer + 4U);
} else if (buffer[2U] == 0x30U || buffer[2U] == 0x31U) {
m_ber.P25FEC(buffer + 4U);
} else if (buffer[2U] == 0x40U) {
m_ber.NXDNFEC(buffer + 4U, buffer[3U]);
} else if (m_hwType == HWT_MMDVM && m_mode != STATE_DMR && m_mode != STATE_P25 && m_mode != STATE_NXDN) {
CUtils::dump("Response", buffer, length);
}
}
bool CMMDVMCal::setFrequency()
{
unsigned char buffer[16U];
buffer[0U] = MMDVM_FRAME_START;
buffer[1U] = 13U;
buffer[2U] = MMDVM_SET_FREQ;
buffer[3U] = 0x00U;
buffer[4U] = (m_frequency >> 0) & 0xFFU;
buffer[5U] = (m_frequency >> 8) & 0xFFU;
buffer[6U] = (m_frequency >> 16) & 0xFFU;
buffer[7U] = (m_frequency >> 24) & 0xFFU;
buffer[8U] = (m_frequency >> 0) & 0xFFU;
buffer[9U] = (m_frequency >> 8) & 0xFFU;
buffer[10U] = (m_frequency >> 16) & 0xFFU;
buffer[11U] = (m_frequency >> 24) & 0xFFU;
buffer[12U] = (unsigned char)(m_power * 2.55F + 0.5F);
int ret = m_serial.write(buffer, 13U);
if (ret != 13U)
return false;
unsigned int count = 0U;
RESP_TYPE_MMDVM resp;
do {
sleep(10U);
resp = getResponse();
if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) {
count++;
if (count >= MAX_RESPONSES) {
::fprintf(stderr, "The MMDVM is not responding to the SET_FREQ command" EOL);
return false;
}
}
} while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK);
if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) {
::fprintf(stderr, "Received a NAK to the SET_FREQ command from the modem, %u" EOL, m_buffer[4U]);
return false;
}
return true;
}
bool CMMDVMCal::getStatus()
{
unsigned char buffer[16U];
buffer[0U] = MMDVM_FRAME_START;
buffer[1U] = 3U;
buffer[2U] = MMDVM_GET_STATUS;
int ret = m_serial.write(buffer, 3U);
if (ret != 3)
return false;
unsigned int count = 0U;
RESP_TYPE_MMDVM resp;
do {
sleep(10U);
resp = getResponse();
if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) {
count++;
if (count >= MAX_RESPONSES) {
::fprintf(stderr, "The MMDVM is not responding to the GET_STATUS command" EOL);
return false;
}
}
} while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK);
if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) {
::fprintf(stderr, "Received a NAK to the GET_STATUS command from the modem, %u" EOL, m_buffer[4U]);
return false;
}
return true;
}
RESP_TYPE_MMDVM CMMDVMCal::getResponse()
{
if (m_offset == 0U) {
// Get the start of the frame or nothing at all
int ret = m_serial.read(m_buffer + 0U, 1U);
if (ret < 0) {
::fprintf(stderr, "Error when reading from the modem" EOL);
return RTM_ERROR;
}
if (ret == 0)
return RTM_TIMEOUT;
if (m_buffer[0U] != MMDVM_FRAME_START)
return RTM_TIMEOUT;
m_offset = 1U;
}
if (m_offset == 1U) {
// Get the length of the frame
int ret = m_serial.read(m_buffer + 1U, 1U);
if (ret < 0) {
::fprintf(stderr, "Error when reading from the modem" EOL);
m_offset = 0U;
return RTM_ERROR;
}
if (ret == 0)
return RTM_TIMEOUT;
if (m_buffer[1U] >= 250U) {
::fprintf(stderr, "Invalid length received from the modem - %u" EOL, m_buffer[1U]);
m_offset = 0U;
return RTM_ERROR;
}
m_length = m_buffer[1U];
m_offset = 2U;
}
if (m_offset == 2U) {
// Get the frame type
int ret = m_serial.read(m_buffer + 2U, 1U);
if (ret < 0) {
::fprintf(stderr, "Error when reading from the modem" EOL);
m_offset = 0U;
return RTM_ERROR;
}
if (ret == 0)
return RTM_TIMEOUT;
m_offset = 3U;
}
if (m_offset >= 3U) {
// Use later two byte length field
if (m_length == 0U) {
int ret = m_serial.read(m_buffer + 3U, 2U);
if (ret < 0) {
::fprintf(stderr, "Error when reading from the modem" EOL);
m_offset = 0U;
return RTM_ERROR;
}
if (ret == 0)
return RTM_TIMEOUT;
m_length = (m_buffer[3U] << 8) | m_buffer[4U];
m_offset = 5U;
}
while (m_offset < m_length) {
int ret = m_serial.read(m_buffer + m_offset, m_length - m_offset);
if (ret < 0) {
::fprintf(stderr, "Error when reading from the modem" EOL);
m_offset = 0U;
return RTM_ERROR;
}
if (ret == 0)
return RTM_TIMEOUT;
if (ret > 0)
m_offset += ret;
}
}
m_offset = 0;
return RTM_OK;
}
void CMMDVMCal::sleep(unsigned int ms)
{
#if defined(_WIN32) || defined(_WIN64)
::Sleep(ms);
#else
::usleep(ms * 1000);
#endif
}