mirror of https://github.com/g4klx/MMDVM.git
Merge remote-tracking branch 'g4klx/c4fmdemod' into c4fmdemod
This commit is contained in:
commit
1b48c4de1c
|
@ -2,3 +2,5 @@
|
||||||
obj/
|
obj/
|
||||||
bin/
|
bin/
|
||||||
STM32F4XX_Lib/
|
STM32F4XX_Lib/
|
||||||
|
GitVersion.h
|
||||||
|
build/
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2009-2015 by Jonathan Naylor G4KLX
|
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
||||||
* Copyright (C) 2016 by Colin Durbridge G4EML
|
* Copyright (C) 2016 by Colin Durbridge G4EML
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
@ -17,8 +17,6 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// #define WANT_DEBUG
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "CWIdTX.h"
|
#include "CWIdTX.h"
|
||||||
|
@ -77,6 +75,8 @@ const struct {
|
||||||
{'/', 0xEAE80000U, 16U},
|
{'/', 0xEAE80000U, 16U},
|
||||||
{'?', 0xAEEA0000U, 18U},
|
{'?', 0xAEEA0000U, 18U},
|
||||||
{',', 0xEEAEE000U, 22U},
|
{',', 0xEEAEE000U, 22U},
|
||||||
|
{'-', 0xEAAE0000U, 18U},
|
||||||
|
{'=', 0xEAB80000U, 16U},
|
||||||
{' ', 0x00000000U, 4U},
|
{' ', 0x00000000U, 4U},
|
||||||
{0U, 0x00000000U, 0U}
|
{0U, 0x00000000U, 0U}
|
||||||
};
|
};
|
||||||
|
|
5
Config.h
5
Config.h
|
@ -60,11 +60,6 @@
|
||||||
// Use separate mode pins to switch external filters/bandwidth for example
|
// Use separate mode pins to switch external filters/bandwidth for example
|
||||||
// #define STM32F4_NUCLEO_MODE_PINS
|
// #define STM32F4_NUCLEO_MODE_PINS
|
||||||
|
|
||||||
// To use wider C4FSK filters for DMR, System Fusion and P25 on transmit
|
|
||||||
// #define WIDE_C4FSK_FILTERS_TX
|
|
||||||
// To use wider C4FSK filters for DMR, System Fusion and P25 on receive
|
|
||||||
// #define WIDE_C4FSK_FILTERS_RX
|
|
||||||
|
|
||||||
// Pass RSSI information to the host
|
// Pass RSSI information to the host
|
||||||
// #define SEND_RSSI_DATA
|
// #define SEND_RSSI_DATA
|
||||||
|
|
||||||
|
|
48
DMRDMORX.cpp
48
DMRDMORX.cpp
|
@ -16,10 +16,6 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define WANT_DEBUG
|
|
||||||
|
|
||||||
// #define DUMP_SAMPLES
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "DMRDMORX.h"
|
#include "DMRDMORX.h"
|
||||||
|
@ -147,9 +143,6 @@ bool CDMRDMORX::processSample(q15_t sample, uint16_t rssi)
|
||||||
case DT_DATA_HEADER:
|
case DT_DATA_HEADER:
|
||||||
DEBUG4("DMRDMORX: data header found pos/centre/threshold", m_syncPtr, centre, threshold);
|
DEBUG4("DMRDMORX: data header found pos/centre/threshold", m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_state = DMORXS_DATA;
|
m_state = DMORXS_DATA;
|
||||||
m_type = 0x00U;
|
m_type = 0x00U;
|
||||||
break;
|
break;
|
||||||
|
@ -159,27 +152,18 @@ bool CDMRDMORX::processSample(q15_t sample, uint16_t rssi)
|
||||||
if (m_state == DMORXS_DATA) {
|
if (m_state == DMORXS_DATA) {
|
||||||
DEBUG4("DMRDMORX: data payload found pos/centre/threshold", m_syncPtr, centre, threshold);
|
DEBUG4("DMRDMORX: data payload found pos/centre/threshold", m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_type = dataType;
|
m_type = dataType;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DT_VOICE_LC_HEADER:
|
case DT_VOICE_LC_HEADER:
|
||||||
DEBUG4("DMRDMORX: voice header found pos/centre/threshold", m_syncPtr, centre, threshold);
|
DEBUG4("DMRDMORX: voice header found pos/centre/threshold", m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_state = DMORXS_VOICE;
|
m_state = DMORXS_VOICE;
|
||||||
break;
|
break;
|
||||||
case DT_VOICE_PI_HEADER:
|
case DT_VOICE_PI_HEADER:
|
||||||
if (m_state == DMORXS_VOICE) {
|
if (m_state == DMORXS_VOICE) {
|
||||||
DEBUG4("DMRDMORX: voice pi header found pos/centre/threshold", m_syncPtr, centre, threshold);
|
DEBUG4("DMRDMORX: voice pi header found pos/centre/threshold", m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
m_state = DMORXS_VOICE;
|
m_state = DMORXS_VOICE;
|
||||||
break;
|
break;
|
||||||
|
@ -187,18 +171,12 @@ bool CDMRDMORX::processSample(q15_t sample, uint16_t rssi)
|
||||||
if (m_state == DMORXS_VOICE) {
|
if (m_state == DMORXS_VOICE) {
|
||||||
DEBUG4("DMRDMORX: voice terminator found pos/centre/threshold", m_syncPtr, centre, threshold);
|
DEBUG4("DMRDMORX: voice terminator found pos/centre/threshold", m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default: // DT_CSBK
|
default: // DT_CSBK
|
||||||
DEBUG4("DMRDMORX: csbk found pos/centre/threshold", m_syncPtr, centre, threshold);
|
DEBUG4("DMRDMORX: csbk found pos/centre/threshold", m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
reset();
|
reset();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -207,9 +185,6 @@ bool CDMRDMORX::processSample(q15_t sample, uint16_t rssi)
|
||||||
// Voice sync
|
// Voice sync
|
||||||
DEBUG4("DMRDMORX: voice sync found pos/centre/threshold", m_syncPtr, centre, threshold);
|
DEBUG4("DMRDMORX: voice sync found pos/centre/threshold", m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_state = DMORXS_VOICE;
|
m_state = DMORXS_VOICE;
|
||||||
m_syncCount = 0U;
|
m_syncCount = 0U;
|
||||||
m_n = 0U;
|
m_n = 0U;
|
||||||
|
@ -231,16 +206,10 @@ bool CDMRDMORX::processSample(q15_t sample, uint16_t rssi)
|
||||||
}
|
}
|
||||||
|
|
||||||
serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U);
|
serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
} else if (m_state == DMORXS_DATA) {
|
} else if (m_state == DMORXS_DATA) {
|
||||||
if (m_type != 0x00U) {
|
if (m_type != 0x00U) {
|
||||||
frame[0U] = CONTROL_DATA | m_type;
|
frame[0U] = CONTROL_DATA | m_type;
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,20 +423,3 @@ void CDMRDMORX::writeRSSIData(uint8_t* frame)
|
||||||
serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U);
|
serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
void CDMRDMORX::writeSamples(uint16_t start, uint8_t control)
|
|
||||||
{
|
|
||||||
q15_t samples[DMR_FRAME_LENGTH_SYMBOLS];
|
|
||||||
|
|
||||||
for (uint16_t i = 0U; i < DMR_FRAME_LENGTH_SYMBOLS; i++) {
|
|
||||||
samples[i] = m_buffer[start];
|
|
||||||
|
|
||||||
start += DMR_RADIO_SYMBOL_LENGTH;
|
|
||||||
if (start >= DMO_BUFFER_LENGTH_SAMPLES)
|
|
||||||
start -= DMO_BUFFER_LENGTH_SAMPLES;
|
|
||||||
}
|
|
||||||
|
|
||||||
serial.writeSamples(STATE_DMR, control, samples, DMR_FRAME_LENGTH_SYMBOLS);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -64,7 +64,6 @@ private:
|
||||||
void correlateSync(bool first);
|
void correlateSync(bool first);
|
||||||
void samplesToBits(uint16_t start, uint8_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold);
|
void samplesToBits(uint16_t start, uint8_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold);
|
||||||
void writeRSSIData(uint8_t* frame);
|
void writeRSSIData(uint8_t* frame);
|
||||||
void writeSamples(uint16_t start, uint8_t control);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
20
DMRDMOTX.cpp
20
DMRDMOTX.cpp
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
||||||
* Copyright (C) 2016 by Colin Durbridge G4EML
|
* Copyright (C) 2016 by Colin Durbridge G4EML
|
||||||
|
* Copyright (C) 2017 by Andy Uribe CA6JAU
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,31 +18,22 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// #define WANT_DEBUG
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "DMRSlotType.h"
|
#include "DMRSlotType.h"
|
||||||
|
|
||||||
#if defined(WIDE_C4FSK_FILTERS_TX)
|
|
||||||
// Generated using rcosdesign(0.2, 4, 5, 'sqrt') in MATLAB
|
|
||||||
static q15_t DMR_C4FSK_FILTER[] = {0, 0, 0, 0, 688, -680, -2158, -3060, -2724, -775, 2684, 7041, 11310, 14425, 15565, 14425,
|
|
||||||
11310, 7041, 2684, -775, -2724, -3060, -2158, -680, 688}; // numTaps = 25, L = 5
|
|
||||||
const uint16_t DMR_C4FSK_FILTER_PHASE_LEN = 5U; // phaseLength = numTaps/L
|
|
||||||
#else
|
|
||||||
// Generated using rcosdesign(0.2, 8, 5, 'sqrt') in MATLAB
|
// Generated using rcosdesign(0.2, 8, 5, 'sqrt') in MATLAB
|
||||||
static q15_t DMR_C4FSK_FILTER[] = {0, 0, 0, 0, 401, 104, -340, -731, -847, -553, 112, 909, 1472, 1450, 683, -675, -2144, -3040, -2706, -770, 2667, 6995,
|
static q15_t RRC_0_2_FILTER[] = {0, 0, 0, 0, 401, 104, -340, -731, -847, -553, 112, 909, 1472, 1450, 683, -675, -2144, -3040, -2706, -770, 2667, 6995,
|
||||||
11237, 14331, 15464, 14331, 11237, 6995, 2667, -770, -2706, -3040, -2144, -675, 683, 1450, 1472, 909, 112,
|
11237, 14331, 15464, 14331, 11237, 6995, 2667, -770, -2706, -3040, -2144, -675, 683, 1450, 1472, 909, 112,
|
||||||
-553, -847, -731, -340, 104, 401}; // numTaps = 45, L = 5
|
-553, -847, -731, -340, 104, 401}; // numTaps = 45, L = 5
|
||||||
const uint16_t DMR_C4FSK_FILTER_PHASE_LEN = 9U; // phaseLength = numTaps/L
|
const uint16_t RRC_0_2_FILTER_PHASE_LEN = 9U; // phaseLength = numTaps/L
|
||||||
#endif
|
|
||||||
|
|
||||||
const q15_t DMR_LEVELA = 2889;
|
const q15_t DMR_LEVELA = 2889;
|
||||||
const q15_t DMR_LEVELB = 963;
|
const q15_t DMR_LEVELB = 963;
|
||||||
const q15_t DMR_LEVELC = -963;
|
const q15_t DMR_LEVELC = -963;
|
||||||
const q15_t DMR_LEVELD = -2889;
|
const q15_t DMR_LEVELD = -2889;
|
||||||
|
|
||||||
// The PR FILL and Data Sync pattern.
|
// The PR FILL and BS Data Sync pattern.
|
||||||
const uint8_t IDLE_DATA[] =
|
const uint8_t IDLE_DATA[] =
|
||||||
{0x53U, 0xC2U, 0x5EU, 0xABU, 0xA8U, 0x67U, 0x1DU, 0xC7U, 0x38U, 0x3BU, 0xD9U,
|
{0x53U, 0xC2U, 0x5EU, 0xABU, 0xA8U, 0x67U, 0x1DU, 0xC7U, 0x38U, 0x3BU, 0xD9U,
|
||||||
0x36U, 0x00U, 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U, 0x03U, 0xF6U,
|
0x36U, 0x00U, 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U, 0x03U, 0xF6U,
|
||||||
|
@ -77,8 +69,8 @@ m_cachPtr(0U)
|
||||||
::memset(m_modState, 0x00U, 16U * sizeof(q15_t));
|
::memset(m_modState, 0x00U, 16U * sizeof(q15_t));
|
||||||
|
|
||||||
m_modFilter.L = DMR_RADIO_SYMBOL_LENGTH;
|
m_modFilter.L = DMR_RADIO_SYMBOL_LENGTH;
|
||||||
m_modFilter.phaseLength = DMR_C4FSK_FILTER_PHASE_LEN;
|
m_modFilter.phaseLength = RRC_0_2_FILTER_PHASE_LEN;
|
||||||
m_modFilter.pCoeffs = DMR_C4FSK_FILTER;
|
m_modFilter.pCoeffs = RRC_0_2_FILTER;
|
||||||
m_modFilter.pState = m_modState;
|
m_modFilter.pState = m_modState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,6 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define WANT_DEBUG
|
|
||||||
|
|
||||||
// #define DUMP_SAMPLES
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "DMRIdleRX.h"
|
#include "DMRIdleRX.h"
|
||||||
|
@ -161,9 +157,6 @@ void CDMRIdleRX::processSample(q15_t sample)
|
||||||
if (colorCode == m_colorCode && dataType == DT_CSBK) {
|
if (colorCode == m_colorCode && dataType == DT_CSBK) {
|
||||||
frame[0U] = CONTROL_IDLE | CONTROL_DATA | DT_CSBK;
|
frame[0U] = CONTROL_IDLE | CONTROL_DATA | DT_CSBK;
|
||||||
serial.writeDMRData(false, frame, DMR_FRAME_LENGTH_BYTES + 1U);
|
serial.writeDMRData(false, frame, DMR_FRAME_LENGTH_BYTES + 1U);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_endPtr = NOENDPTR;
|
m_endPtr = NOENDPTR;
|
||||||
|
@ -216,20 +209,3 @@ void CDMRIdleRX::setColorCode(uint8_t colorCode)
|
||||||
{
|
{
|
||||||
m_colorCode = colorCode;
|
m_colorCode = colorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
void CDMRIdleRX::writeSamples(uint16_t start, uint8_t control)
|
|
||||||
{
|
|
||||||
q15_t samples[DMR_FRAME_LENGTH_SYMBOLS];
|
|
||||||
|
|
||||||
for (uint16_t i = 0U; i < DMR_FRAME_LENGTH_SYMBOLS; i++) {
|
|
||||||
samples[i] = m_buffer[start];
|
|
||||||
|
|
||||||
start += DMR_RADIO_SYMBOL_LENGTH;
|
|
||||||
if (start >= DMR_FRAME_LENGTH_SAMPLES)
|
|
||||||
start -= DMR_FRAME_LENGTH_SAMPLES;
|
|
||||||
}
|
|
||||||
|
|
||||||
serial.writeSamples(STATE_DMR, control, samples, DMR_FRAME_LENGTH_SYMBOLS);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -45,7 +45,6 @@ private:
|
||||||
|
|
||||||
void processSample(q15_t sample);
|
void processSample(q15_t sample);
|
||||||
void samplesToBits(uint16_t start, uint8_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold);
|
void samplesToBits(uint16_t start, uint8_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold);
|
||||||
void writeSamples(uint16_t start, uint8_t control);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -16,10 +16,6 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define WANT_DEBUG
|
|
||||||
|
|
||||||
// #define DUMP_SAMPLES
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "DMRSlotRX.h"
|
#include "DMRSlotRX.h"
|
||||||
|
@ -150,9 +146,6 @@ bool CDMRSlotRX::processSample(q15_t sample, uint16_t rssi)
|
||||||
case DT_DATA_HEADER:
|
case DT_DATA_HEADER:
|
||||||
DEBUG5("DMRSlotRX: data header found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
DEBUG5("DMRSlotRX: data header found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_state = DMRRXS_DATA;
|
m_state = DMRRXS_DATA;
|
||||||
m_type = 0x00U;
|
m_type = 0x00U;
|
||||||
break;
|
break;
|
||||||
|
@ -162,27 +155,18 @@ bool CDMRSlotRX::processSample(q15_t sample, uint16_t rssi)
|
||||||
if (m_state == DMRRXS_DATA) {
|
if (m_state == DMRRXS_DATA) {
|
||||||
DEBUG5("DMRSlotRX: data payload found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
DEBUG5("DMRSlotRX: data payload found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_type = dataType;
|
m_type = dataType;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DT_VOICE_LC_HEADER:
|
case DT_VOICE_LC_HEADER:
|
||||||
DEBUG5("DMRSlotRX: voice header found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
DEBUG5("DMRSlotRX: voice header found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_state = DMRRXS_VOICE;
|
m_state = DMRRXS_VOICE;
|
||||||
break;
|
break;
|
||||||
case DT_VOICE_PI_HEADER:
|
case DT_VOICE_PI_HEADER:
|
||||||
if (m_state == DMRRXS_VOICE) {
|
if (m_state == DMRRXS_VOICE) {
|
||||||
DEBUG5("DMRSlotRX: voice pi header found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
DEBUG5("DMRSlotRX: voice pi header found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
m_state = DMRRXS_VOICE;
|
m_state = DMRRXS_VOICE;
|
||||||
break;
|
break;
|
||||||
|
@ -190,9 +174,6 @@ bool CDMRSlotRX::processSample(q15_t sample, uint16_t rssi)
|
||||||
if (m_state == DMRRXS_VOICE) {
|
if (m_state == DMRRXS_VOICE) {
|
||||||
DEBUG5("DMRSlotRX: voice terminator found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
DEBUG5("DMRSlotRX: voice terminator found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_state = DMRRXS_NONE;
|
m_state = DMRRXS_NONE;
|
||||||
m_endPtr = NOENDPTR;
|
m_endPtr = NOENDPTR;
|
||||||
}
|
}
|
||||||
|
@ -200,9 +181,6 @@ bool CDMRSlotRX::processSample(q15_t sample, uint16_t rssi)
|
||||||
default: // DT_CSBK
|
default: // DT_CSBK
|
||||||
DEBUG5("DMRSlotRX: csbk found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
DEBUG5("DMRSlotRX: csbk found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_state = DMRRXS_NONE;
|
m_state = DMRRXS_NONE;
|
||||||
m_endPtr = NOENDPTR;
|
m_endPtr = NOENDPTR;
|
||||||
break;
|
break;
|
||||||
|
@ -212,9 +190,6 @@ bool CDMRSlotRX::processSample(q15_t sample, uint16_t rssi)
|
||||||
// Voice sync
|
// Voice sync
|
||||||
DEBUG5("DMRSlotRX: voice sync found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
DEBUG5("DMRSlotRX: voice sync found slot/pos/centre/threshold", m_slot ? 2U : 1U, m_syncPtr, centre, threshold);
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_state = DMRRXS_VOICE;
|
m_state = DMRRXS_VOICE;
|
||||||
m_syncCount = 0U;
|
m_syncCount = 0U;
|
||||||
m_n = 0U;
|
m_n = 0U;
|
||||||
|
@ -237,16 +212,10 @@ bool CDMRSlotRX::processSample(q15_t sample, uint16_t rssi)
|
||||||
}
|
}
|
||||||
|
|
||||||
serial.writeDMRData(m_slot, frame, DMR_FRAME_LENGTH_BYTES + 1U);
|
serial.writeDMRData(m_slot, frame, DMR_FRAME_LENGTH_BYTES + 1U);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
} else if (m_state == DMRRXS_DATA) {
|
} else if (m_state == DMRRXS_DATA) {
|
||||||
if (m_type != 0x00U) {
|
if (m_type != 0x00U) {
|
||||||
frame[0U] = CONTROL_DATA | m_type;
|
frame[0U] = CONTROL_DATA | m_type;
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(ptr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,17 +400,3 @@ void CDMRSlotRX::writeRSSIData(uint8_t* frame)
|
||||||
serial.writeDMRData(m_slot, frame, DMR_FRAME_LENGTH_BYTES + 1U);
|
serial.writeDMRData(m_slot, frame, DMR_FRAME_LENGTH_BYTES + 1U);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
void CDMRSlotRX::writeSamples(uint16_t start, uint8_t control)
|
|
||||||
{
|
|
||||||
q15_t samples[DMR_FRAME_LENGTH_SYMBOLS];
|
|
||||||
|
|
||||||
for (uint16_t i = 0U; i < DMR_FRAME_LENGTH_SYMBOLS; i++) {
|
|
||||||
samples[i] = m_buffer[start];
|
|
||||||
start += DMR_RADIO_SYMBOL_LENGTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
serial.writeSamples(STATE_DMR, control, samples, DMR_FRAME_LENGTH_SYMBOLS);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -67,7 +67,6 @@ private:
|
||||||
void correlateSync(bool first);
|
void correlateSync(bool first);
|
||||||
void samplesToBits(uint16_t start, uint8_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold);
|
void samplesToBits(uint16_t start, uint8_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold);
|
||||||
void writeRSSIData(uint8_t* frame);
|
void writeRSSIData(uint8_t* frame);
|
||||||
void writeSamples(uint16_t start, uint8_t control);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
20
DMRTX.cpp
20
DMRTX.cpp
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
||||||
* Copyright (C) 2016 by Colin Durbridge G4EML
|
* Copyright (C) 2016 by Colin Durbridge G4EML
|
||||||
|
* Copyright (C) 2017 by Andy Uribe CA6JAU
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,31 +18,22 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// #define WANT_DEBUG
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "DMRSlotType.h"
|
#include "DMRSlotType.h"
|
||||||
|
|
||||||
#if defined(WIDE_C4FSK_FILTERS_TX)
|
|
||||||
// Generated using rcosdesign(0.2, 4, 5, 'sqrt') in MATLAB
|
|
||||||
static q15_t DMR_C4FSK_FILTER[] = {0, 0, 0, 0, 688, -680, -2158, -3060, -2724, -775, 2684, 7041, 11310, 14425, 15565, 14425,
|
|
||||||
11310, 7041, 2684, -775, -2724, -3060, -2158, -680, 688}; // numTaps = 25, L = 5
|
|
||||||
const uint16_t DMR_C4FSK_FILTER_PHASE_LEN = 5U; // phaseLength = numTaps/L
|
|
||||||
#else
|
|
||||||
// Generated using rcosdesign(0.2, 8, 5, 'sqrt') in MATLAB
|
// Generated using rcosdesign(0.2, 8, 5, 'sqrt') in MATLAB
|
||||||
static q15_t DMR_C4FSK_FILTER[] = {0, 0, 0, 0, 401, 104, -340, -731, -847, -553, 112, 909, 1472, 1450, 683, -675, -2144, -3040, -2706, -770, 2667, 6995,
|
static q15_t RRC_0_2_FILTER[] = {0, 0, 0, 0, 401, 104, -340, -731, -847, -553, 112, 909, 1472, 1450, 683, -675, -2144, -3040, -2706, -770, 2667, 6995,
|
||||||
11237, 14331, 15464, 14331, 11237, 6995, 2667, -770, -2706, -3040, -2144, -675, 683, 1450, 1472, 909, 112,
|
11237, 14331, 15464, 14331, 11237, 6995, 2667, -770, -2706, -3040, -2144, -675, 683, 1450, 1472, 909, 112,
|
||||||
-553, -847, -731, -340, 104, 401}; // numTaps = 45, L = 5
|
-553, -847, -731, -340, 104, 401}; // numTaps = 45, L = 5
|
||||||
const uint16_t DMR_C4FSK_FILTER_PHASE_LEN = 9U; // phaseLength = numTaps/L
|
const uint16_t RRC_0_2_FILTER_PHASE_LEN = 9U; // phaseLength = numTaps/L
|
||||||
#endif
|
|
||||||
|
|
||||||
const q15_t DMR_LEVELA = 2889;
|
const q15_t DMR_LEVELA = 2889;
|
||||||
const q15_t DMR_LEVELB = 963;
|
const q15_t DMR_LEVELB = 963;
|
||||||
const q15_t DMR_LEVELC = -963;
|
const q15_t DMR_LEVELC = -963;
|
||||||
const q15_t DMR_LEVELD = -2889;
|
const q15_t DMR_LEVELD = -2889;
|
||||||
|
|
||||||
// The PR FILL and Data Sync pattern.
|
// The PR FILL and BS Data Sync pattern.
|
||||||
const uint8_t IDLE_DATA[] =
|
const uint8_t IDLE_DATA[] =
|
||||||
{0x53U, 0xC2U, 0x5EU, 0xABU, 0xA8U, 0x67U, 0x1DU, 0xC7U, 0x38U, 0x3BU, 0xD9U,
|
{0x53U, 0xC2U, 0x5EU, 0xABU, 0xA8U, 0x67U, 0x1DU, 0xC7U, 0x38U, 0x3BU, 0xD9U,
|
||||||
0x36U, 0x00U, 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U, 0x03U, 0xF6U,
|
0x36U, 0x00U, 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U, 0x03U, 0xF6U,
|
||||||
|
@ -82,8 +74,8 @@ m_abort()
|
||||||
::memset(m_modState, 0x00U, 16U * sizeof(q15_t));
|
::memset(m_modState, 0x00U, 16U * sizeof(q15_t));
|
||||||
|
|
||||||
m_modFilter.L = DMR_RADIO_SYMBOL_LENGTH;
|
m_modFilter.L = DMR_RADIO_SYMBOL_LENGTH;
|
||||||
m_modFilter.phaseLength = DMR_C4FSK_FILTER_PHASE_LEN;
|
m_modFilter.phaseLength = RRC_0_2_FILTER_PHASE_LEN;
|
||||||
m_modFilter.pCoeffs = DMR_C4FSK_FILTER;
|
m_modFilter.pCoeffs = RRC_0_2_FILTER;
|
||||||
m_modFilter.pState = m_modState;
|
m_modFilter.pState = m_modState;
|
||||||
|
|
||||||
::memcpy(m_newShortLC, EMPTY_SHORT_LC, 12U);
|
::memcpy(m_newShortLC, EMPTY_SHORT_LC, 12U);
|
||||||
|
|
36
DStarRX.cpp
36
DStarRX.cpp
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
||||||
|
* Copyright (C) 2017 by Andy Uribe CA6JAU
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,8 +17,6 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// #define WANT_DEBUG
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "DStarRX.h"
|
#include "DStarRX.h"
|
||||||
|
@ -35,9 +34,9 @@ const unsigned int SYNC_POS = 21U * DSTAR_DATA_LENGTH_BITS;
|
||||||
const unsigned int SYNC_SCAN_START = SYNC_POS - 3U;
|
const unsigned int SYNC_SCAN_START = SYNC_POS - 3U;
|
||||||
const unsigned int SYNC_SCAN_END = SYNC_POS + 3U;
|
const unsigned int SYNC_SCAN_END = SYNC_POS + 3U;
|
||||||
|
|
||||||
// Generated using [b, a] = butter(1, 0.002) in MATLAB
|
// Generated using [b, a] = butter(1, 0.001) in MATLAB
|
||||||
static q15_t DC_FILTER[] = {103, 0, 103, 0, 32563, 0}; // {b0, 0, b1, b2, -a1, -a2}
|
static q31_t DC_FILTER[] = {3367972, 0, 3367972, 0, 2140747704, 0}; // {b0, 0, b1, b2, -a1, -a2}
|
||||||
const uint16_t DC_FILTER_STAGES = 1U; // One Biquad stage
|
const uint32_t DC_FILTER_STAGES = 1U; // One Biquad stage
|
||||||
|
|
||||||
// D-Star bit order version of 0x55 0x55 0x6E 0x0A
|
// D-Star bit order version of 0x55 0x55 0x6E 0x0A
|
||||||
const uint32_t FRAME_SYNC_DATA = 0x00557650U;
|
const uint32_t FRAME_SYNC_DATA = 0x00557650U;
|
||||||
|
@ -262,14 +261,16 @@ m_pathMemory2(),
|
||||||
m_pathMemory3(),
|
m_pathMemory3(),
|
||||||
m_fecOutput(),
|
m_fecOutput(),
|
||||||
m_rssiAccum(0U),
|
m_rssiAccum(0U),
|
||||||
m_rssiCount(0U)
|
m_rssiCount(0U),
|
||||||
|
m_dcFilter(),
|
||||||
|
m_dcState()
|
||||||
{
|
{
|
||||||
::memset(m_DCState, 0x00U, 4U * sizeof(q15_t));
|
::memset(m_dcState, 0x00U, 4U * sizeof(q31_t));
|
||||||
|
|
||||||
m_DCFilter.numStages = DC_FILTER_STAGES;
|
m_dcFilter.numStages = DC_FILTER_STAGES;
|
||||||
m_DCFilter.pState = m_DCState;
|
m_dcFilter.pState = m_dcState;
|
||||||
m_DCFilter.pCoeffs = DC_FILTER;
|
m_dcFilter.pCoeffs = DC_FILTER;
|
||||||
m_DCFilter.postShift = 0;
|
m_dcFilter.postShift = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDStarRX::reset()
|
void CDStarRX::reset()
|
||||||
|
@ -287,12 +288,14 @@ void CDStarRX::reset()
|
||||||
void CDStarRX::samples(const q15_t* samples, const uint16_t* rssi, uint8_t length)
|
void CDStarRX::samples(const q15_t* samples, const uint16_t* rssi, uint8_t length)
|
||||||
{
|
{
|
||||||
q31_t dc_level = 0;
|
q31_t dc_level = 0;
|
||||||
q15_t DCVals[20];
|
q31_t dcVals[20];
|
||||||
|
q31_t intSamp[20];
|
||||||
|
|
||||||
::arm_biquad_cascade_df1_q15(&m_DCFilter, (q15_t*)samples, DCVals, length);
|
::arm_q15_to_q31((q15_t*)samples, intSamp, length);
|
||||||
|
::arm_biquad_cascade_df1_q31(&m_dcFilter, intSamp, dcVals, length);
|
||||||
|
|
||||||
for (uint8_t i = 0U; i < length; i++)
|
for (uint8_t i = 0U; i < length; i++)
|
||||||
dc_level += (q31_t)DCVals[i];
|
dc_level += dcVals[i];
|
||||||
|
|
||||||
dc_level /= length;
|
dc_level /= length;
|
||||||
|
|
||||||
|
@ -300,7 +303,7 @@ void CDStarRX::samples(const q15_t* samples, const uint16_t* rssi, uint8_t lengt
|
||||||
m_rssiAccum += rssi[i];
|
m_rssiAccum += rssi[i];
|
||||||
m_rssiCount++;
|
m_rssiCount++;
|
||||||
|
|
||||||
bool bit = samples[i] < (q15_t)dc_level;
|
bool bit = samples[i] < (q15_t) (dc_level >> 16);
|
||||||
|
|
||||||
if (bit != m_prev) {
|
if (bit != m_prev) {
|
||||||
if (m_pll < (PLLMAX / 2U))
|
if (m_pll < (PLLMAX / 2U))
|
||||||
|
@ -434,12 +437,11 @@ void CDStarRX::processData(bool bit)
|
||||||
bool syncSeen = false;
|
bool syncSeen = false;
|
||||||
if (m_dataBits >= SYNC_SCAN_START && m_dataBits <= (SYNC_POS + 1U)) {
|
if (m_dataBits >= SYNC_SCAN_START && m_dataBits <= (SYNC_POS + 1U)) {
|
||||||
if (countBits32((m_patternBuffer & DATA_SYNC_MASK) ^ DATA_SYNC_DATA) <= DATA_SYNC_ERRS) {
|
if (countBits32((m_patternBuffer & DATA_SYNC_MASK) ^ DATA_SYNC_DATA) <= DATA_SYNC_ERRS) {
|
||||||
#if defined(WANT_DEBUG)
|
|
||||||
if (m_dataBits < SYNC_POS)
|
if (m_dataBits < SYNC_POS)
|
||||||
DEBUG2("DStarRX: found data sync in Data, early", SYNC_POS - m_dataBits);
|
DEBUG2("DStarRX: found data sync in Data, early", SYNC_POS - m_dataBits);
|
||||||
else
|
else
|
||||||
DEBUG1("DStarRX: found data sync in Data");
|
DEBUG1("DStarRX: found data sync in Data");
|
||||||
#endif
|
|
||||||
m_rxBufferBits = DSTAR_DATA_LENGTH_BITS;
|
m_rxBufferBits = DSTAR_DATA_LENGTH_BITS;
|
||||||
m_dataBits = 0U;
|
m_dataBits = 0U;
|
||||||
syncSeen = true;
|
syncSeen = true;
|
||||||
|
|
|
@ -53,9 +53,8 @@ private:
|
||||||
uint8_t m_fecOutput[42U];
|
uint8_t m_fecOutput[42U];
|
||||||
uint32_t m_rssiAccum;
|
uint32_t m_rssiAccum;
|
||||||
uint16_t m_rssiCount;
|
uint16_t m_rssiCount;
|
||||||
|
arm_biquad_casd_df1_inst_q31 m_dcFilter;
|
||||||
arm_biquad_casd_df1_inst_q15 m_DCFilter;
|
q31_t m_dcState[4];
|
||||||
q15_t m_DCState[4];
|
|
||||||
|
|
||||||
void processNone(bool bit);
|
void processNone(bool bit);
|
||||||
void processHeader(bool bit);
|
void processHeader(bool bit);
|
||||||
|
|
11
DStarTX.cpp
11
DStarTX.cpp
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
||||||
|
* Copyright (C) 2017 by Andy Uribe CA6JAU
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,8 +17,6 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define WANT_DEBUG
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "DStarTX.h"
|
#include "DStarTX.h"
|
||||||
|
@ -29,8 +28,8 @@ const uint8_t BIT_SYNC = 0xAAU;
|
||||||
const uint8_t FRAME_SYNC[] = {0xEAU, 0xA6U, 0x00U};
|
const uint8_t FRAME_SYNC[] = {0xEAU, 0xA6U, 0x00U};
|
||||||
|
|
||||||
// Generated using gaussfir(0.35, 1, 5) in MATLAB
|
// Generated using gaussfir(0.35, 1, 5) in MATLAB
|
||||||
static q15_t DSTAR_GMSK_FILTER[] = {0, 0, 0, 0, 212, 743, 1974, 3965, 6026, 6929, 6026, 3965, 1974, 743, 212}; // numTaps = 15, L = 5
|
static q15_t GAUSSIAN_0_35_FILTER[] = {0, 0, 0, 0, 212, 743, 1974, 3965, 6026, 6929, 6026, 3965, 1974, 743, 212}; // numTaps = 15, L = 5
|
||||||
const uint16_t DSTAR_GMSK_FILTER_PHASE_LEN = 3U; // phaseLength = numTaps/L
|
const uint16_t GAUSSIAN_0_35_FILTER_PHASE_LEN = 3U; // phaseLength = numTaps/L
|
||||||
|
|
||||||
const q15_t DSTAR_LEVEL0 = -4000;
|
const q15_t DSTAR_LEVEL0 = -4000;
|
||||||
const q15_t DSTAR_LEVEL1 = 4000;
|
const q15_t DSTAR_LEVEL1 = 4000;
|
||||||
|
@ -200,8 +199,8 @@ m_txDelay(60U) // 100ms
|
||||||
::memset(m_modState, 0x00U, 20U * sizeof(q15_t));
|
::memset(m_modState, 0x00U, 20U * sizeof(q15_t));
|
||||||
|
|
||||||
m_modFilter.L = DSTAR_RADIO_BIT_LENGTH;
|
m_modFilter.L = DSTAR_RADIO_BIT_LENGTH;
|
||||||
m_modFilter.phaseLength = DSTAR_GMSK_FILTER_PHASE_LEN;
|
m_modFilter.phaseLength = GAUSSIAN_0_35_FILTER_PHASE_LEN;
|
||||||
m_modFilter.pCoeffs = DSTAR_GMSK_FILTER;
|
m_modFilter.pCoeffs = GAUSSIAN_0_35_FILTER;
|
||||||
m_modFilter.pState = m_modState;
|
m_modFilter.pState = m_modState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
Debug.h
16
Debug.h
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
|
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,9 +20,6 @@
|
||||||
#define DEBUG_H
|
#define DEBUG_H
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
#if defined(WANT_DEBUG)
|
|
||||||
|
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
|
|
||||||
#define DEBUG1(a) serial.writeDebug((a))
|
#define DEBUG1(a) serial.writeDebug((a))
|
||||||
|
@ -32,16 +29,5 @@
|
||||||
#define DEBUG5(a,b,c,d,e) serial.writeDebug((a),(b),(c),(d),(e))
|
#define DEBUG5(a,b,c,d,e) serial.writeDebug((a),(b),(c),(d),(e))
|
||||||
#define ASSERT(a) serial.writeAssert((a),#a,__FILE__,__LINE__)
|
#define ASSERT(a) serial.writeAssert((a),#a,__FILE__,__LINE__)
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#define DEBUG1(a)
|
|
||||||
#define DEBUG2(a,b)
|
|
||||||
#define DEBUG3(a,b,c)
|
|
||||||
#define DEBUG4(a,b,c,d)
|
|
||||||
#define DEBUG5(a,b,c,d,e)
|
|
||||||
#define ASSERT(a)
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
97
IO.cpp
97
IO.cpp
|
@ -18,32 +18,28 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// #define WANT_DEBUG
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "IO.h"
|
#include "IO.h"
|
||||||
|
|
||||||
#if defined(WIDE_C4FSK_FILTERS_RX)
|
|
||||||
// Generated using rcosdesign(0.2, 4, 5, 'sqrt') in MATLAB
|
|
||||||
static q15_t C4FSK_FILTER[] = {688, -680, -2158, -3060, -2724, -775, 2684, 7041, 11310, 14425, 15565, 14425,
|
|
||||||
11310, 7041, 2684, -775, -2724, -3060, -2158, -680, 688, 0};
|
|
||||||
const uint16_t C4FSK_FILTER_LEN = 22U;
|
|
||||||
#else
|
|
||||||
// Generated using rcosdesign(0.2, 8, 5, 'sqrt') in MATLAB
|
// Generated using rcosdesign(0.2, 8, 5, 'sqrt') in MATLAB
|
||||||
static q15_t C4FSK_FILTER[] = {401, 104, -340, -731, -847, -553, 112, 909, 1472, 1450, 683, -675, -2144, -3040, -2706, -770, 2667, 6995,
|
static q15_t RRC_0_2_FILTER[] = {401, 104, -340, -731, -847, -553, 112, 909, 1472, 1450, 683, -675, -2144, -3040, -2706, -770, 2667, 6995,
|
||||||
11237, 14331, 15464, 14331, 11237, 6995, 2667, -770, -2706, -3040, -2144, -675, 683, 1450, 1472, 909, 112,
|
11237, 14331, 15464, 14331, 11237, 6995, 2667, -770, -2706, -3040, -2144, -675, 683, 1450, 1472, 909, 112,
|
||||||
-553, -847, -731, -340, 104, 401, 0};
|
-553, -847, -731, -340, 104, 401, 0};
|
||||||
const uint16_t C4FSK_FILTER_LEN = 42U;
|
const uint16_t RRC_0_2_FILTER_LEN = 42U;
|
||||||
#endif
|
|
||||||
|
// Generated using rcosdesign(0.2, 8, 5, 'normal') in MATLAB
|
||||||
|
static q15_t RC_0_2_FILTER[] = {0, -413, -750, -845, -587, 0, 741, 1347, 1520, 1062, 0, -1383, -2582, -3021, -2222, 0, 3434, 7487, 11318, 14054, 15044, 14054,
|
||||||
|
11318, 7487, 3434, 0, -2222, -3021, -2582, -1383, 0, 1062, 1520, 1347, 741, 0, -587, -845, -750, -413};
|
||||||
|
const uint16_t RC_0_2_FILTER_LEN = 40U;
|
||||||
|
|
||||||
// Generated using gaussfir(0.5, 4, 5) in MATLAB
|
// Generated using gaussfir(0.5, 4, 5) in MATLAB
|
||||||
static q15_t GMSK_FILTER[] = {8, 104, 760, 3158, 7421, 9866, 7421, 3158, 760, 104, 8, 0};
|
static q15_t GAUSSIAN_0_5_FILTER[] = {8, 104, 760, 3158, 7421, 9866, 7421, 3158, 760, 104, 8, 0};
|
||||||
const uint16_t GMSK_FILTER_LEN = 12U;
|
const uint16_t GAUSSIAN_0_5_FILTER_LEN = 12U;
|
||||||
|
|
||||||
// One symbol boxcar filter
|
// One symbol boxcar filter
|
||||||
static q15_t P25_FILTER[] = {3000, 3000, 3000, 3000, 3000, 0};
|
static q15_t BOXCAR_FILTER[] = {3000, 3000, 3000, 3000, 3000, 0};
|
||||||
const uint16_t P25_FILTER_LEN = 6U;
|
const uint16_t BOXCAR_FILTER_LEN = 6U;
|
||||||
|
|
||||||
const uint16_t DC_OFFSET = 2048U;
|
const uint16_t DC_OFFSET = 2048U;
|
||||||
|
|
||||||
|
@ -52,12 +48,14 @@ m_started(false),
|
||||||
m_rxBuffer(RX_RINGBUFFER_SIZE),
|
m_rxBuffer(RX_RINGBUFFER_SIZE),
|
||||||
m_txBuffer(TX_RINGBUFFER_SIZE),
|
m_txBuffer(TX_RINGBUFFER_SIZE),
|
||||||
m_rssiBuffer(RX_RINGBUFFER_SIZE),
|
m_rssiBuffer(RX_RINGBUFFER_SIZE),
|
||||||
m_C4FSKFilter(),
|
m_rrcFilter(),
|
||||||
m_GMSKFilter(),
|
m_rcFilter(),
|
||||||
m_P25Filter(),
|
m_gaussianFilter(),
|
||||||
m_C4FSKState(),
|
m_boxcarFilter(),
|
||||||
m_GMSKState(),
|
m_rrcState(),
|
||||||
m_P25State(),
|
m_rcState(),
|
||||||
|
m_gaussianState(),
|
||||||
|
m_boxcarState(),
|
||||||
m_pttInvert(false),
|
m_pttInvert(false),
|
||||||
m_rxLevel(128 * 128),
|
m_rxLevel(128 * 128),
|
||||||
m_cwIdTXLevel(128 * 128),
|
m_cwIdTXLevel(128 * 128),
|
||||||
|
@ -73,21 +71,26 @@ m_dacOverflow(0U),
|
||||||
m_watchdog(0U),
|
m_watchdog(0U),
|
||||||
m_lockout(false)
|
m_lockout(false)
|
||||||
{
|
{
|
||||||
::memset(m_C4FSKState, 0x00U, 70U * sizeof(q15_t));
|
::memset(m_rrcState, 0x00U, 70U * sizeof(q15_t));
|
||||||
::memset(m_GMSKState, 0x00U, 40U * sizeof(q15_t));
|
::memset(m_rcState, 0x00U, 70U * sizeof(q15_t));
|
||||||
::memset(m_P25State, 0x00U, 30U * sizeof(q15_t));
|
::memset(m_gaussianState, 0x00U, 40U * sizeof(q15_t));
|
||||||
|
::memset(m_boxcarState, 0x00U, 30U * sizeof(q15_t));
|
||||||
|
|
||||||
m_C4FSKFilter.numTaps = C4FSK_FILTER_LEN;
|
m_rrcFilter.numTaps = RRC_0_2_FILTER_LEN;
|
||||||
m_C4FSKFilter.pState = m_C4FSKState;
|
m_rrcFilter.pState = m_rrcState;
|
||||||
m_C4FSKFilter.pCoeffs = C4FSK_FILTER;
|
m_rrcFilter.pCoeffs = RRC_0_2_FILTER;
|
||||||
|
|
||||||
m_GMSKFilter.numTaps = GMSK_FILTER_LEN;
|
m_rcFilter.numTaps = RC_0_2_FILTER_LEN;
|
||||||
m_GMSKFilter.pState = m_GMSKState;
|
m_rcFilter.pState = m_rcState;
|
||||||
m_GMSKFilter.pCoeffs = GMSK_FILTER;
|
m_rcFilter.pCoeffs = RC_0_2_FILTER;
|
||||||
|
|
||||||
m_P25Filter.numTaps = P25_FILTER_LEN;
|
m_gaussianFilter.numTaps = GAUSSIAN_0_5_FILTER_LEN;
|
||||||
m_P25Filter.pState = m_P25State;
|
m_gaussianFilter.pState = m_gaussianState;
|
||||||
m_P25Filter.pCoeffs = P25_FILTER;
|
m_gaussianFilter.pCoeffs = GAUSSIAN_0_5_FILTER;
|
||||||
|
|
||||||
|
m_boxcarFilter.numTaps = BOXCAR_FILTER_LEN;
|
||||||
|
m_boxcarFilter.pState = m_boxcarState;
|
||||||
|
m_boxcarFilter.pCoeffs = BOXCAR_FILTER;
|
||||||
|
|
||||||
initInt();
|
initInt();
|
||||||
}
|
}
|
||||||
|
@ -169,21 +172,21 @@ void CIO::process()
|
||||||
if (m_modemState == STATE_IDLE) {
|
if (m_modemState == STATE_IDLE) {
|
||||||
if (m_dstarEnable) {
|
if (m_dstarEnable) {
|
||||||
q15_t GMSKVals[RX_BLOCK_SIZE];
|
q15_t GMSKVals[RX_BLOCK_SIZE];
|
||||||
::arm_fir_fast_q15(&m_GMSKFilter, samples, GMSKVals, RX_BLOCK_SIZE);
|
::arm_fir_fast_q15(&m_gaussianFilter, samples, GMSKVals, RX_BLOCK_SIZE);
|
||||||
|
|
||||||
dstarRX.samples(GMSKVals, rssi, RX_BLOCK_SIZE);
|
dstarRX.samples(GMSKVals, rssi, RX_BLOCK_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_p25Enable) {
|
if (m_p25Enable) {
|
||||||
q15_t P25Vals[RX_BLOCK_SIZE];
|
q15_t P25Vals[RX_BLOCK_SIZE];
|
||||||
::arm_fir_fast_q15(&m_P25Filter, samples, P25Vals, RX_BLOCK_SIZE);
|
::arm_fir_fast_q15(&m_boxcarFilter, samples, P25Vals, RX_BLOCK_SIZE);
|
||||||
|
|
||||||
p25RX.samples(P25Vals, rssi, RX_BLOCK_SIZE);
|
p25RX.samples(P25Vals, rssi, RX_BLOCK_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_dmrEnable || m_ysfEnable) {
|
if (m_dmrEnable || m_ysfEnable) {
|
||||||
q15_t C4FSKVals[RX_BLOCK_SIZE];
|
q15_t C4FSKVals[RX_BLOCK_SIZE];
|
||||||
::arm_fir_fast_q15(&m_C4FSKFilter, samples, C4FSKVals, RX_BLOCK_SIZE);
|
::arm_fir_fast_q15(&m_rrcFilter, samples, C4FSKVals, RX_BLOCK_SIZE);
|
||||||
|
|
||||||
if (m_dmrEnable) {
|
if (m_dmrEnable) {
|
||||||
if (m_duplex)
|
if (m_duplex)
|
||||||
|
@ -198,14 +201,14 @@ void CIO::process()
|
||||||
} else if (m_modemState == STATE_DSTAR) {
|
} else if (m_modemState == STATE_DSTAR) {
|
||||||
if (m_dstarEnable) {
|
if (m_dstarEnable) {
|
||||||
q15_t GMSKVals[RX_BLOCK_SIZE];
|
q15_t GMSKVals[RX_BLOCK_SIZE];
|
||||||
::arm_fir_fast_q15(&m_GMSKFilter, samples, GMSKVals, RX_BLOCK_SIZE);
|
::arm_fir_fast_q15(&m_gaussianFilter, samples, GMSKVals, RX_BLOCK_SIZE);
|
||||||
|
|
||||||
dstarRX.samples(GMSKVals, rssi, RX_BLOCK_SIZE);
|
dstarRX.samples(GMSKVals, rssi, RX_BLOCK_SIZE);
|
||||||
}
|
}
|
||||||
} else if (m_modemState == STATE_DMR) {
|
} else if (m_modemState == STATE_DMR) {
|
||||||
if (m_dmrEnable) {
|
if (m_dmrEnable) {
|
||||||
q15_t C4FSKVals[RX_BLOCK_SIZE];
|
q15_t C4FSKVals[RX_BLOCK_SIZE];
|
||||||
::arm_fir_fast_q15(&m_C4FSKFilter, samples, C4FSKVals, RX_BLOCK_SIZE);
|
::arm_fir_fast_q15(&m_rrcFilter, samples, C4FSKVals, RX_BLOCK_SIZE);
|
||||||
|
|
||||||
if (m_duplex) {
|
if (m_duplex) {
|
||||||
// If the transmitter isn't on, use the DMR idle RX to detect the wakeup CSBKs
|
// If the transmitter isn't on, use the DMR idle RX to detect the wakeup CSBKs
|
||||||
|
@ -220,20 +223,23 @@ void CIO::process()
|
||||||
} else if (m_modemState == STATE_YSF) {
|
} else if (m_modemState == STATE_YSF) {
|
||||||
if (m_ysfEnable) {
|
if (m_ysfEnable) {
|
||||||
q15_t C4FSKVals[RX_BLOCK_SIZE];
|
q15_t C4FSKVals[RX_BLOCK_SIZE];
|
||||||
::arm_fir_fast_q15(&m_C4FSKFilter, samples, C4FSKVals, RX_BLOCK_SIZE);
|
::arm_fir_fast_q15(&m_rrcFilter, samples, C4FSKVals, RX_BLOCK_SIZE);
|
||||||
|
|
||||||
ysfRX.samples(C4FSKVals, rssi, RX_BLOCK_SIZE);
|
ysfRX.samples(C4FSKVals, rssi, RX_BLOCK_SIZE);
|
||||||
}
|
}
|
||||||
} else if (m_modemState == STATE_P25) {
|
} else if (m_modemState == STATE_P25) {
|
||||||
if (m_p25Enable) {
|
if (m_p25Enable) {
|
||||||
q15_t P25Vals[RX_BLOCK_SIZE];
|
q15_t P25Vals1[RX_BLOCK_SIZE];
|
||||||
::arm_fir_fast_q15(&m_P25Filter, samples, P25Vals, RX_BLOCK_SIZE);
|
::arm_fir_fast_q15(&m_boxcarFilter, samples, P25Vals1, RX_BLOCK_SIZE);
|
||||||
|
|
||||||
p25RX.samples(P25Vals, rssi, RX_BLOCK_SIZE);
|
q15_t P25Vals2[RX_BLOCK_SIZE];
|
||||||
|
::arm_fir_fast_q15(&m_rcFilter, P25Vals1, P25Vals2, RX_BLOCK_SIZE);
|
||||||
|
|
||||||
|
p25RX.samples(P25Vals2, rssi, RX_BLOCK_SIZE);
|
||||||
}
|
}
|
||||||
} else if (m_modemState == STATE_DSTARCAL) {
|
} else if (m_modemState == STATE_DSTARCAL) {
|
||||||
q15_t GMSKVals[RX_BLOCK_SIZE];
|
q15_t GMSKVals[RX_BLOCK_SIZE];
|
||||||
::arm_fir_fast_q15(&m_GMSKFilter, samples, GMSKVals, RX_BLOCK_SIZE);
|
::arm_fir_fast_q15(&m_gaussianFilter, samples, GMSKVals, RX_BLOCK_SIZE);
|
||||||
|
|
||||||
calDStarRX.samples(GMSKVals, RX_BLOCK_SIZE);
|
calDStarRX.samples(GMSKVals, RX_BLOCK_SIZE);
|
||||||
} else if (m_modemState == STATE_RSSICAL) {
|
} else if (m_modemState == STATE_RSSICAL) {
|
||||||
|
@ -346,11 +352,6 @@ void CIO::getOverflow(bool& adcOverflow, bool& dacOverflow)
|
||||||
adcOverflow = m_adcOverflow > 0U;
|
adcOverflow = m_adcOverflow > 0U;
|
||||||
dacOverflow = m_dacOverflow > 0U;
|
dacOverflow = m_dacOverflow > 0U;
|
||||||
|
|
||||||
#if defined(WANT_DEBUG)
|
|
||||||
if (m_adcOverflow > 0U || m_dacOverflow > 0U)
|
|
||||||
DEBUG3("IO: adc/dac", m_adcOverflow, m_dacOverflow);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_adcOverflow = 0U;
|
m_adcOverflow = 0U;
|
||||||
m_dacOverflow = 0U;
|
m_dacOverflow = 0U;
|
||||||
}
|
}
|
||||||
|
|
14
IO.h
14
IO.h
|
@ -60,12 +60,14 @@ private:
|
||||||
CSampleRB m_txBuffer;
|
CSampleRB m_txBuffer;
|
||||||
CRSSIRB m_rssiBuffer;
|
CRSSIRB m_rssiBuffer;
|
||||||
|
|
||||||
arm_fir_instance_q15 m_C4FSKFilter;
|
arm_fir_instance_q15 m_rrcFilter;
|
||||||
arm_fir_instance_q15 m_GMSKFilter;
|
arm_fir_instance_q15 m_rcFilter;
|
||||||
arm_fir_instance_q15 m_P25Filter;
|
arm_fir_instance_q15 m_gaussianFilter;
|
||||||
q15_t m_C4FSKState[70U]; // NoTaps + BlockSize - 1, 42 + 20 - 1 plus some spare
|
arm_fir_instance_q15 m_boxcarFilter;
|
||||||
q15_t m_GMSKState[40U]; // NoTaps + BlockSize - 1, 12 + 20 - 1 plus some spare
|
q15_t m_rrcState[70U]; // NoTaps + BlockSize - 1, 42 + 20 - 1 plus some spare
|
||||||
q15_t m_P25State[30U]; // NoTaps + BlockSize - 1, 6 + 20 - 1 plus some spare
|
q15_t m_rcState[70U]; // NoTaps + BlockSize - 1, 42 + 20 - 1 plus some spare
|
||||||
|
q15_t m_gaussianState[40U]; // NoTaps + BlockSize - 1, 12 + 20 - 1 plus some spare
|
||||||
|
q15_t m_boxcarState[30U]; // NoTaps + BlockSize - 1, 6 + 20 - 1 plus some spare
|
||||||
|
|
||||||
bool m_pttInvert;
|
bool m_pttInvert;
|
||||||
q15_t m_rxLevel;
|
q15_t m_rxLevel;
|
||||||
|
|
21
Makefile
21
Makefile
|
@ -36,19 +36,19 @@ ifdef SYSTEMROOT
|
||||||
ASOURCES=$(shell dir /S /B *.s)
|
ASOURCES=$(shell dir /S /B *.s)
|
||||||
CSOURCES=$(shell dir /S /B *.c)
|
CSOURCES=$(shell dir /S /B *.c)
|
||||||
CXXSOURCES=$(shell dir /S /B *.cpp)
|
CXXSOURCES=$(shell dir /S /B *.cpp)
|
||||||
CLEANCMD=del /S *.o *.hex *.bin *.elf
|
CLEANCMD=del /S *.o *.hex *.bin *.elf GitVersion.h
|
||||||
MDBIN=md $@
|
MDBIN=md $@
|
||||||
else ifdef SystemRoot
|
else ifdef SystemRoot
|
||||||
ASOURCES=$(shell dir /S /B *.s)
|
ASOURCES=$(shell dir /S /B *.s)
|
||||||
CSOURCES=$(shell dir /S /B *.c)
|
CSOURCES=$(shell dir /S /B *.c)
|
||||||
CXXSOURCES=$(shell dir /S /B *.cpp)
|
CXXSOURCES=$(shell dir /S /B *.cpp)
|
||||||
CLEANCMD=del /S *.o *.hex *.bin *.elf
|
CLEANCMD=del /S *.o *.hex *.bin *.elf GitVersion.h
|
||||||
MDBIN=md $@
|
MDBIN=md $@
|
||||||
else
|
else
|
||||||
ASOURCES=$(shell find . -name '*.s')
|
ASOURCES=$(shell find . -name '*.s')
|
||||||
CSOURCES=$(shell find . -name '*.c')
|
CSOURCES=$(shell find . -name '*.c')
|
||||||
CXXSOURCES=$(shell find . -name '*.cpp')
|
CXXSOURCES=$(shell find . -name '*.cpp')
|
||||||
CLEANCMD=rm -f $(OBJECTS) $(BINDIR)/$(BINELF) $(BINDIR)/$(BINHEX) $(BINDIR)/$(BINBIN)
|
CLEANCMD=rm -f $(OBJECTS) $(BINDIR)/$(BINELF) $(BINDIR)/$(BINHEX) $(BINDIR)/$(BINBIN) GitVersion.h
|
||||||
MDBIN=mkdir $@
|
MDBIN=mkdir $@
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -85,11 +85,11 @@ MCFLAGS=-mcpu=cortex-m4 -mthumb -mlittle-endian \
|
||||||
|
|
||||||
# COMPILE FLAGS
|
# COMPILE FLAGS
|
||||||
# STM32F4 Discovery board:
|
# STM32F4 Discovery board:
|
||||||
DEFS_DIS=-DUSE_STDPERIPH_DRIVER -DSTM32F4XX -DSTM32F40_41xxx -DSTM32F4_DISCOVERY -DHSE_VALUE=$(OSC)
|
DEFS_DIS=-DUSE_STDPERIPH_DRIVER -DSTM32F4XX -DSTM32F40_41xxx -DSTM32F4_DISCOVERY -DHSE_VALUE=$(OSC) -DMADEBYMAKEFILE
|
||||||
# Pi board:
|
# Pi board:
|
||||||
DEFS_PI=-DUSE_STDPERIPH_DRIVER -DSTM32F4XX -DSTM32F446xx -DSTM32F4_PI -DARDUINO_MODE_PINS -DSEND_RSSI_DATA -DSERIAL_REPEATER -DHSE_VALUE=$(OSC)
|
DEFS_PI=-DUSE_STDPERIPH_DRIVER -DSTM32F4XX -DSTM32F446xx -DSTM32F4_PI -DARDUINO_MODE_PINS -DSEND_RSSI_DATA -DSERIAL_REPEATER -DHSE_VALUE=$(OSC) -DMADEBYMAKEFILE
|
||||||
# STM32F4 Nucleo 446 board:
|
# STM32F4 Nucleo 446 board:
|
||||||
DEFS_NUCLEO=-DUSE_STDPERIPH_DRIVER -DSTM32F4XX -DSTM32F446xx -DSTM32F4_NUCLEO -DHSE_VALUE=$(OSC)
|
DEFS_NUCLEO=-DUSE_STDPERIPH_DRIVER -DSTM32F4XX -DSTM32F446xx -DSTM32F4_NUCLEO -DHSE_VALUE=$(OSC) -DMADEBYMAKEFILE
|
||||||
|
|
||||||
CFLAGS=-c $(MCFLAGS) $(INCLUDES)
|
CFLAGS=-c $(MCFLAGS) $(INCLUDES)
|
||||||
CXXFLAGS=-c $(MCFLAGS) $(INCLUDES)
|
CXXFLAGS=-c $(MCFLAGS) $(INCLUDES)
|
||||||
|
@ -109,6 +109,7 @@ pi: CXXFLAGS+=$(DEFS_PI) -Os -fno-exceptions -ffunction-sections -fdata-sections
|
||||||
pi: LDFLAGS+=-Os --specs=nano.specs
|
pi: LDFLAGS+=-Os --specs=nano.specs
|
||||||
pi: release
|
pi: release
|
||||||
|
|
||||||
|
nucleo: GitVersion.h
|
||||||
nucleo: CFLAGS+=$(DEFS_NUCLEO) -Os -ffunction-sections -fdata-sections -fno-builtin -Wno-implicit -DCUSTOM_NEW -DNO_EXCEPTIONS
|
nucleo: CFLAGS+=$(DEFS_NUCLEO) -Os -ffunction-sections -fdata-sections -fno-builtin -Wno-implicit -DCUSTOM_NEW -DNO_EXCEPTIONS
|
||||||
nucleo: CXXFLAGS+=$(DEFS_NUCLEO) -Os -fno-exceptions -ffunction-sections -fdata-sections -fno-builtin -fno-rtti -DCUSTOM_NEW -DNO_EXCEPTIONS
|
nucleo: CXXFLAGS+=$(DEFS_NUCLEO) -Os -fno-exceptions -ffunction-sections -fdata-sections -fno-builtin -fno-rtti -DCUSTOM_NEW -DNO_EXCEPTIONS
|
||||||
nucleo: LDFLAGS+=-Os --specs=nano.specs
|
nucleo: LDFLAGS+=-Os --specs=nano.specs
|
||||||
|
@ -171,3 +172,11 @@ endif
|
||||||
ifneq ($(wildcard /opt/openocd/bin/openocd),)
|
ifneq ($(wildcard /opt/openocd/bin/openocd),)
|
||||||
/opt/openocd/bin/openocd -f /opt/openocd/share/openocd/scripts/board/stm32f4discovery.cfg -c "program bin/$(BINELF) verify reset exit"
|
/opt/openocd/bin/openocd -f /opt/openocd/share/openocd/scripts/board/stm32f4discovery.cfg -c "program bin/$(BINELF) verify reset exit"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Export the current git version if the index file exists, else 000...
|
||||||
|
GitVersion.h:
|
||||||
|
ifneq ("$(wildcard .git/index)","")
|
||||||
|
echo "#define GITVERSION \"$(shell git rev-parse --short HEAD)\"" > $@
|
||||||
|
else
|
||||||
|
echo "#define GITVERSION \"0000000\"" > $@
|
||||||
|
endif
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
#!/usr/bin/make
|
||||||
|
# makefile for the arduino due (works with arduino IDE 1.6.11)
|
||||||
|
#
|
||||||
|
# The original file can be found at https://github.com/pauldreik/arduino-due-makefile
|
||||||
|
#
|
||||||
|
# USAGE: put this file in the same dir as your .ino file is.
|
||||||
|
# configure the PORT variable and ADIR at the top of the file
|
||||||
|
# to match your local configuration.
|
||||||
|
# Type make upload to compile and upload.
|
||||||
|
#
|
||||||
|
|
||||||
|
#user specific settings:
|
||||||
|
#where to find the IDE
|
||||||
|
ADIR:=$(HOME)/.arduino15
|
||||||
|
#which serial port to use (add a file with SUBSYSTEMS=="usb",
|
||||||
|
#ATTRS{product}=="Arduino Due Prog. Port", ATTRS{idProduct}=="003d",
|
||||||
|
#ATTRS{idVendor}=="2341", SYMLINK+="arduino_due" in /etc/udev/rules.d/
|
||||||
|
#to get this working). Do not prefix the port with /dev/, just take
|
||||||
|
#the basename.
|
||||||
|
PORT:=ttyACM0
|
||||||
|
#if you want to verify the bossac upload, define this to -v
|
||||||
|
VERIFY:=-v
|
||||||
|
|
||||||
|
|
||||||
|
#end of user configuration.
|
||||||
|
|
||||||
|
|
||||||
|
#then some general settings. They should not be necessary to modify.
|
||||||
|
#CXX:=$(ADIR)/tools/g++_arm_none_eabi/bin/arm-none-eabi-g++
|
||||||
|
CXX:=$(ADIR)/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/arm-none-eabi-g++
|
||||||
|
#CC:=$(ADIR)/tools/g++_arm_none_eabi/bin/arm-none-eabi-gcc
|
||||||
|
CC:=$(ADIR)/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/arm-none-eabi-gcc
|
||||||
|
OBJCOPY:=$(ADIR)/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/arm-none-eabi-objcopy
|
||||||
|
|
||||||
|
C:=$(CC)
|
||||||
|
#SAM:=arduino/sam/
|
||||||
|
SAM:=$(ADIR)/packages/arduino/hardware/sam/1.6.11
|
||||||
|
#CMSIS:=arduino/sam/system/CMSIS/
|
||||||
|
#LIBSAM:=arduino/sam/system/libsam
|
||||||
|
TMPDIR:=$(PWD)/build
|
||||||
|
AR:=$(ADIR)/tools/g++_arm_none_eabi/bin/arm-none-eabi-ar
|
||||||
|
AR:=$(ADIR)/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/arm-none-eabi-ar
|
||||||
|
|
||||||
|
|
||||||
|
#all these values are hard coded and should maybe be configured somehow else,
|
||||||
|
#like olikraus does in his makefile.
|
||||||
|
DEFINES:=-Dprintf=iprintf -DF_CPU=84000000 -DARDUINO=10611 -D__SAM3X8E__ -DUSB_PID=0x003e -DUSB_VID=0x2341 -DUSBCON \
|
||||||
|
-DARDUINO_SAM_DUE -DARDUINO_ARCH_SAM '-DUSB_MANUFACTURER="Arduino LLC"' '-DUSB_PRODUCT="Arduino Due"' \
|
||||||
|
-DMADEBYMAKEFILE
|
||||||
|
|
||||||
|
INCLUDES:=-I$(SAM)/system/libsam -I$(SAM)/system/CMSIS/CMSIS/Include/ \
|
||||||
|
-I$(SAM)/system/CMSIS/Device/ATMEL/ -I$(SAM)/cores/arduino \
|
||||||
|
-I$(SAM)/variants/arduino_due_x
|
||||||
|
|
||||||
|
#also include the current dir for convenience
|
||||||
|
INCLUDES += -I.
|
||||||
|
|
||||||
|
#compilation flags common to both c and c++
|
||||||
|
COMMON_FLAGS:=-g -Os -w -ffunction-sections -fdata-sections -nostdlib \
|
||||||
|
--param max-inline-insns-single=500 -mcpu=cortex-m3 -mthumb \
|
||||||
|
-fno-threadsafe-statics
|
||||||
|
#for compiling c (do not warn, this is not our code)
|
||||||
|
CFLAGS:=$(COMMON_FLAGS) -std=gnu11
|
||||||
|
#for compiling c++
|
||||||
|
CXXFLAGS:=$(COMMON_FLAGS) -fno-rtti -fno-exceptions -std=gnu++11 -Wall -Wextra
|
||||||
|
|
||||||
|
#let the results be named after the project
|
||||||
|
PROJNAME:=$(shell basename *.ino .ino)
|
||||||
|
|
||||||
|
#we will make a new mainfile from the ino file.
|
||||||
|
NEWMAINFILE:=$(TMPDIR)/$(PROJNAME).ino.cpp
|
||||||
|
|
||||||
|
#our own sourcefiles is the (converted) ino file and any local cpp files
|
||||||
|
MYSRCFILES:=$(NEWMAINFILE) $(shell ls *.cpp 2>/dev/null)
|
||||||
|
MYOBJFILES:=$(addsuffix .o,$(addprefix $(TMPDIR)/,$(notdir $(MYSRCFILES))))
|
||||||
|
|
||||||
|
#These source files are the ones forming core.a
|
||||||
|
CORESRCXX:=$(shell ls ${SAM}/cores/arduino/*.cpp ${SAM}/cores/arduino/USB/*.cpp ${SAM}/variants/arduino_due_x/variant.cpp)
|
||||||
|
CORESRC:=$(shell ls ${SAM}/cores/arduino/*.c)
|
||||||
|
|
||||||
|
#hey this one is needed too: $(SAM)/cores/arduino/wiring_pulse_asm.S" add -x assembler-with-cpp
|
||||||
|
#and this one: /1.6.11/cores/arduino/avr/dtostrf.c but it seems to work
|
||||||
|
#anyway, probably because I do not use that functionality.
|
||||||
|
|
||||||
|
#convert the core source files to object files. assume no clashes.
|
||||||
|
COREOBJSXX:=$(addprefix $(TMPDIR)/core/,$(notdir $(CORESRCXX)) )
|
||||||
|
COREOBJSXX:=$(addsuffix .o,$(COREOBJSXX))
|
||||||
|
COREOBJS:=$(addprefix $(TMPDIR)/core/,$(notdir $(CORESRC)) )
|
||||||
|
COREOBJS:=$(addsuffix .o,$(COREOBJS))
|
||||||
|
|
||||||
|
default:
|
||||||
|
@echo default rule, does nothing. Try make compile or make upload.
|
||||||
|
|
||||||
|
#This rule is good to just make sure stuff compiles, without having to wait
|
||||||
|
#for bossac.
|
||||||
|
compile: GitVersion.h
|
||||||
|
compile: $(TMPDIR)/$(PROJNAME).elf
|
||||||
|
|
||||||
|
#This is a make rule template to create object files from the source files.
|
||||||
|
# arg 1=src file
|
||||||
|
# arg 2=object file
|
||||||
|
# arg 3= XX if c++, empty if c
|
||||||
|
define OBJ_template
|
||||||
|
$(2): $(1)
|
||||||
|
$(C$(3)) -MD -c $(C$(3)FLAGS) $(DEFINES) $(INCLUDES) $(1) -o $(2)
|
||||||
|
endef
|
||||||
|
#now invoke the template both for c++ sources
|
||||||
|
$(foreach src,$(CORESRCXX), $(eval $(call OBJ_template,$(src),$(addsuffix .o,$(addprefix $(TMPDIR)/core/,$(notdir $(src)))),XX) ) )
|
||||||
|
#...and for c sources:
|
||||||
|
$(foreach src,$(CORESRC), $(eval $(call OBJ_template,$(src),$(addsuffix .o,$(addprefix $(TMPDIR)/core/,$(notdir $(src)))),) ) )
|
||||||
|
|
||||||
|
#and our own c++ sources
|
||||||
|
$(foreach src,$(MYSRCFILES), $(eval $(call OBJ_template,$(src),$(addsuffix .o,$(addprefix $(TMPDIR)/,$(notdir $(src)))),XX) ) )
|
||||||
|
|
||||||
|
|
||||||
|
clean:
|
||||||
|
test ! -d $(TMPDIR) || rm -rf $(TMPDIR)
|
||||||
|
$(RM) GitVersion.h
|
||||||
|
|
||||||
|
.PHONY: .FORCE upload default
|
||||||
|
|
||||||
|
$(TMPDIR):
|
||||||
|
mkdir -p $(TMPDIR)
|
||||||
|
|
||||||
|
$(TMPDIR)/core:
|
||||||
|
mkdir -p $(TMPDIR)/core
|
||||||
|
|
||||||
|
#creates the cpp file from the .ino file
|
||||||
|
$(NEWMAINFILE): $(PROJNAME).ino
|
||||||
|
cat $(SAM)/cores/arduino/main.cpp > $(NEWMAINFILE)
|
||||||
|
cat $(PROJNAME).ino >> $(NEWMAINFILE)
|
||||||
|
echo 'extern "C" void __cxa_pure_virtual() {while (true);}' >> $(NEWMAINFILE)
|
||||||
|
|
||||||
|
#include the dependencies for our own files
|
||||||
|
-include $(MYOBJFILES:.o=.d)
|
||||||
|
|
||||||
|
#create the core library from the core objects. Do this EXACTLY as the
|
||||||
|
#arduino IDE does it, seems *really* picky about this.
|
||||||
|
#Sorry for the hard coding.
|
||||||
|
$(TMPDIR)/core.a: $(TMPDIR)/core $(COREOBJS) $(COREOBJSXX)
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/wiring_shift.c.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/wiring_analog.c.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/itoa.c.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/cortex_handlers.c.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/hooks.c.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/wiring.c.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/WInterrupts.c.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/syscalls_sam3.c.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/iar_calls_sam3.c.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/wiring_digital.c.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/Print.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/USARTClass.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/WString.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/PluggableUSB.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/USBCore.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/CDC.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/wiring_pulse.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/UARTClass.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/main.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/new.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/watchdog.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/Stream.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/RingBuffer.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/IPAddress.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/Reset.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/WMath.cpp.o
|
||||||
|
$(AR) rcs $(TMPDIR)/core.a $(TMPDIR)/core/variant.cpp.o
|
||||||
|
|
||||||
|
#link our own object files with core to form the elf file
|
||||||
|
$(TMPDIR)/$(PROJNAME).elf: $(TMPDIR)/core.a $(TMPDIR)/core/syscalls_sam3.c.o $(MYOBJFILES)
|
||||||
|
$(CC) -mcpu=cortex-m3 -mthumb -Os -Wl,--gc-sections -T$(SAM)/variants/arduino_due_x/linker_scripts/gcc/flash.ld -Wl,-Map,$(NEWMAINFILE).map -o $@ -L$(TMPDIR) -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry=Reset_Handler -Wl,--unresolved-symbols=report-all -Wl,--warn-common -Wl,--warn-section-align -Wl,--start-group -u _sbrk -u link -u _close -u _fstat -u _isatty -u _lseek -u _read -u _write -u _exit -u kill -u _getpid $(MYOBJFILES) $(TMPDIR)/core/variant.cpp.o $(SAM)/variants/arduino_due_x/libsam_sam3x8e_gcc_rel.a $(SAM)/system/CMSIS/CMSIS/Lib/GCC/libarm_cortexM3l_math.a $(TMPDIR)/core.a -Wl,--end-group -lm -gcc
|
||||||
|
|
||||||
|
#copy from the hex to our bin file (why?)
|
||||||
|
$(TMPDIR)/$(PROJNAME).bin: $(TMPDIR)/$(PROJNAME).elf
|
||||||
|
$(OBJCOPY) -O binary $< $@
|
||||||
|
|
||||||
|
#upload to the arduino by first resetting it (stty) and the running bossac
|
||||||
|
upload: $(TMPDIR)/$(PROJNAME).bin
|
||||||
|
stty -F /dev/$(PORT) cs8 1200 hupcl
|
||||||
|
$(ADIR)/packages/arduino/tools/bossac/1.6.1-arduino/bossac -i --port=$(PORT) -U false -e -w $(VERIFY) -b $(TMPDIR)/$(PROJNAME).bin -R
|
||||||
|
|
||||||
|
# Export the current git version if the index file exists, else 000...
|
||||||
|
GitVersion.h: .FORCE
|
||||||
|
ifneq ("$(wildcard .git/index)","")
|
||||||
|
echo "#define GITVERSION \"$(shell git rev-parse --short HEAD)\"" > $@
|
||||||
|
else
|
||||||
|
echo "#define GITVERSION \"0000000\"" > $@
|
||||||
|
endif
|
||||||
|
|
||||||
|
.FORCE:
|
24
P25RX.cpp
24
P25RX.cpp
|
@ -16,10 +16,6 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define WANT_DEBUG
|
|
||||||
|
|
||||||
// #define DUMP_SAMPLES
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "P25RX.h"
|
#include "P25RX.h"
|
||||||
|
@ -245,9 +241,6 @@ void CP25RX::processLdu(q15_t sample)
|
||||||
} else {
|
} else {
|
||||||
frame[0U] = m_lostCount == (MAX_SYNC_FRAMES - 1U) ? 0x01U : 0x00U;
|
frame[0U] = m_lostCount == (MAX_SYNC_FRAMES - 1U) ? 0x01U : 0x00U;
|
||||||
writeRSSILdu(frame);
|
writeRSSILdu(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(m_lduStartPtr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_maxCorr = 0;
|
m_maxCorr = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,20 +464,3 @@ void CP25RX::writeRSSILdu(uint8_t* ldu)
|
||||||
m_rssiAccum = 0U;
|
m_rssiAccum = 0U;
|
||||||
m_rssiCount = 0U;
|
m_rssiCount = 0U;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
void CP25RX::writeSamples(uint16_t start, uint8_t control)
|
|
||||||
{
|
|
||||||
q15_t samples[P25_LDU_FRAME_LENGTH_SYMBOLS];
|
|
||||||
|
|
||||||
for (uint16_t i = 0U; i < P25_LDU_FRAME_LENGTH_SYMBOLS; i++) {
|
|
||||||
samples[i] = m_buffer[start];
|
|
||||||
|
|
||||||
start += P25_RADIO_SYMBOL_LENGTH;
|
|
||||||
if (start >= P25_LDU_FRAME_LENGTH_SAMPLES)
|
|
||||||
start -= P25_LDU_FRAME_LENGTH_SAMPLES;
|
|
||||||
}
|
|
||||||
|
|
||||||
serial.writeSamples(STATE_P25, control, samples, P25_LDU_FRAME_LENGTH_SYMBOLS);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
1
P25RX.h
1
P25RX.h
|
@ -67,7 +67,6 @@ private:
|
||||||
void calculateLevels(uint16_t start, uint16_t count);
|
void calculateLevels(uint16_t start, uint16_t count);
|
||||||
void samplesToBits(uint16_t start, uint16_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold);
|
void samplesToBits(uint16_t start, uint16_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold);
|
||||||
void writeRSSILdu(uint8_t* ldu);
|
void writeRSSILdu(uint8_t* ldu);
|
||||||
void writeSamples(uint16_t start, uint8_t control);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
26
P25TX.cpp
26
P25TX.cpp
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX
|
* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX
|
||||||
|
* Copyright (C) 2017 by Andy Uribe CA6JAU
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,35 +17,26 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// #define WANT_DEBUG
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "P25TX.h"
|
#include "P25TX.h"
|
||||||
|
|
||||||
#include "P25Defines.h"
|
#include "P25Defines.h"
|
||||||
|
|
||||||
#if defined(WIDE_C4FSK_FILTERS_TX)
|
|
||||||
// Generated using rcosdesign(0.2, 4, 5, 'normal') in MATLAB
|
|
||||||
// numTaps = 20, L = 5
|
|
||||||
static q15_t P25_C4FSK_FILTER[] = {-1392, -2602, -3043, -2238, 0, 3460, 7543, 11400, 14153, 15152, 14153, 11400, 7543, 3460, 0, -2238, -3043, -2602, -1392, 0};
|
|
||||||
const uint16_t P25_C4FSK_FILTER_PHASE_LEN = 4U; // phaseLength = numTaps/L
|
|
||||||
#else
|
|
||||||
// Generated using rcosdesign(0.2, 8, 5, 'normal') in MATLAB
|
// Generated using rcosdesign(0.2, 8, 5, 'normal') in MATLAB
|
||||||
// numTaps = 40, L = 5
|
// numTaps = 40, L = 5
|
||||||
static q15_t P25_C4FSK_FILTER[] = {-413, -751, -845, -587, 0, 740, 1348, 1520, 1063, 0, -1383, -2583, -3021, -2222, 0, 3435, 7488, 11318, 14053, 15044, 14053,
|
static q15_t RC_0_2_FILTER[] = {-413, -751, -845, -587, 0, 740, 1348, 1520, 1063, 0, -1383, -2583, -3021, -2222, 0, 3435, 7488, 11318, 14053, 15044, 14053,
|
||||||
11318, 7488, 3435, 0, -2222, -3021, -2583, -1383, 0, 1063, 1520, 1348, 740, 0, -587, -845, -751, -413, 0};
|
11318, 7488, 3435, 0, -2222, -3021, -2583, -1383, 0, 1063, 1520, 1348, 740, 0, -587, -845, -751, -413, 0};
|
||||||
const uint16_t P25_C4FSK_FILTER_PHASE_LEN = 8U; // phaseLength = numTaps/L
|
const uint16_t RC_0_2_FILTER_PHASE_LEN = 8U; // phaseLength = numTaps/L
|
||||||
#endif
|
|
||||||
|
|
||||||
// Generated in MATLAB using the following commands, and then normalised for unity gain
|
// Generated in MATLAB using the following commands, and then normalised for unity gain
|
||||||
// shape2 = 'Inverse-sinc Lowpass';
|
// shape2 = 'Inverse-sinc Lowpass';
|
||||||
// d2 = fdesign.interpolator(2, shape2);
|
// d2 = fdesign.interpolator(2, shape2);
|
||||||
// h2 = design(d2, 'SystemObject', true);
|
// h2 = design(d2, 'SystemObject', true);
|
||||||
static q15_t P25_LP_FILTER[] = {170, 401, 340, -203, -715, -478, 281, 419, -440, -1002, -103, 1114, 528, -1389, -1520, 1108, 2674, -388, -4662,
|
static q15_t LOWPASS_FILTER[] = {170, 401, 340, -203, -715, -478, 281, 419, -440, -1002, -103, 1114, 528, -1389, -1520, 1108, 2674, -388, -4662,
|
||||||
-2132, 9168, 20241, 20241, 9168, -2132, -4662, -388, 2674, 1108, -1520, -1389, 528, 1114, -103, -1002, -440, 419,
|
-2132, 9168, 20241, 20241, 9168, -2132, -4662, -388, 2674, 1108, -1520, -1389, 528, 1114, -103, -1002, -440, 419,
|
||||||
281, -478, -715, -203, 340, 401, 170};
|
281, -478, -715, -203, 340, 401, 170};
|
||||||
const uint16_t P25_LP_FILTER_LEN = 44U;
|
const uint16_t LOWPASS_FILTER_LEN = 44U;
|
||||||
|
|
||||||
const q15_t P25_LEVELA = 1698;
|
const q15_t P25_LEVELA = 1698;
|
||||||
const q15_t P25_LEVELB = 566;
|
const q15_t P25_LEVELB = 566;
|
||||||
|
@ -68,13 +60,13 @@ m_txDelay(240U) // 200ms
|
||||||
::memset(m_lpState, 0x00U, 70U * sizeof(q15_t));
|
::memset(m_lpState, 0x00U, 70U * sizeof(q15_t));
|
||||||
|
|
||||||
m_modFilter.L = P25_RADIO_SYMBOL_LENGTH;
|
m_modFilter.L = P25_RADIO_SYMBOL_LENGTH;
|
||||||
m_modFilter.phaseLength = P25_C4FSK_FILTER_PHASE_LEN;
|
m_modFilter.phaseLength = RC_0_2_FILTER_PHASE_LEN;
|
||||||
m_modFilter.pCoeffs = P25_C4FSK_FILTER;
|
m_modFilter.pCoeffs = RC_0_2_FILTER;
|
||||||
m_modFilter.pState = m_modState;
|
m_modFilter.pState = m_modState;
|
||||||
|
|
||||||
m_lpFilter.numTaps = P25_LP_FILTER_LEN;
|
m_lpFilter.numTaps = LOWPASS_FILTER_LEN;
|
||||||
m_lpFilter.pState = m_lpState;
|
m_lpFilter.pState = m_lpState;
|
||||||
m_lpFilter.pCoeffs = P25_LP_FILTER;
|
m_lpFilter.pCoeffs = LOWPASS_FILTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CP25TX::process()
|
void CP25TX::process()
|
||||||
|
|
|
@ -17,11 +17,14 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// #define WANT_DEBUG
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
|
|
||||||
|
#if defined(MADEBYMAKEFILE)
|
||||||
|
#include "GitVersion.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#include "SerialPort.h"
|
#include "SerialPort.h"
|
||||||
|
|
||||||
const uint8_t MMDVM_FRAME_START = 0xE0U;
|
const uint8_t MMDVM_FRAME_START = 0xE0U;
|
||||||
|
@ -62,8 +65,6 @@ const uint8_t MMDVM_NAK = 0x7FU;
|
||||||
|
|
||||||
const uint8_t MMDVM_SERIAL = 0x80U;
|
const uint8_t MMDVM_SERIAL = 0x80U;
|
||||||
|
|
||||||
const uint8_t MMDVM_SAMPLES = 0xF0U;
|
|
||||||
|
|
||||||
const uint8_t MMDVM_DEBUG1 = 0xF1U;
|
const uint8_t MMDVM_DEBUG1 = 0xF1U;
|
||||||
const uint8_t MMDVM_DEBUG2 = 0xF2U;
|
const uint8_t MMDVM_DEBUG2 = 0xF2U;
|
||||||
const uint8_t MMDVM_DEBUG3 = 0xF3U;
|
const uint8_t MMDVM_DEBUG3 = 0xF3U;
|
||||||
|
@ -76,8 +77,14 @@ const uint8_t MMDVM_DEBUG5 = 0xF5U;
|
||||||
#else
|
#else
|
||||||
#define DESCRIPTION "MMDVM 20170406 (D-Star/DMR/System Fusion/P25/RSSI/CW Id)"
|
#define DESCRIPTION "MMDVM 20170406 (D-Star/DMR/System Fusion/P25/RSSI/CW Id)"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(GITVERSION)
|
||||||
|
#define concat(a, b) a " GitID #" b ""
|
||||||
|
const char HARDWARE[] = concat(DESCRIPTION, GITVERSION);
|
||||||
|
#else
|
||||||
#define concat(a, b, c) a " (Build: " b " " c ")"
|
#define concat(a, b, c) a " (Build: " b " " c ")"
|
||||||
const char HARDWARE[] = concat(DESCRIPTION, __TIME__, __DATE__);
|
const char HARDWARE[] = concat(DESCRIPTION, __TIME__, __DATE__);
|
||||||
|
#endif
|
||||||
|
|
||||||
const uint8_t PROTOCOL_VERSION = 1U;
|
const uint8_t PROTOCOL_VERSION = 1U;
|
||||||
|
|
||||||
|
@ -85,7 +92,8 @@ const uint8_t PROTOCOL_VERSION = 1U;
|
||||||
CSerialPort::CSerialPort() :
|
CSerialPort::CSerialPort() :
|
||||||
m_buffer(),
|
m_buffer(),
|
||||||
m_ptr(0U),
|
m_ptr(0U),
|
||||||
m_len(0U)
|
m_len(0U),
|
||||||
|
m_debug(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +229,8 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
|
||||||
bool ysfLoDev = (data[0U] & 0x08U) == 0x08U;
|
bool ysfLoDev = (data[0U] & 0x08U) == 0x08U;
|
||||||
bool simplex = (data[0U] & 0x80U) == 0x80U;
|
bool simplex = (data[0U] & 0x80U) == 0x80U;
|
||||||
|
|
||||||
|
m_debug = (data[0U] & 0x10U) == 0x10U;
|
||||||
|
|
||||||
bool dstarEnable = (data[1U] & 0x01U) == 0x01U;
|
bool dstarEnable = (data[1U] & 0x01U) == 0x01U;
|
||||||
bool dmrEnable = (data[1U] & 0x02U) == 0x02U;
|
bool dmrEnable = (data[1U] & 0x02U) == 0x02U;
|
||||||
bool ysfEnable = (data[1U] & 0x04U) == 0x04U;
|
bool ysfEnable = (data[1U] & 0x04U) == 0x04U;
|
||||||
|
@ -920,33 +930,11 @@ void CSerialPort::writeRSSIData(const uint8_t* data, uint8_t length)
|
||||||
writeInt(1U, reply, count);
|
writeInt(1U, reply, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSerialPort::writeSamples(uint8_t mode, uint8_t control, const q15_t* samples, uint16_t nSamples)
|
|
||||||
{
|
|
||||||
uint8_t reply[1800U];
|
|
||||||
|
|
||||||
reply[0U] = MMDVM_FRAME_START;
|
|
||||||
reply[1U] = 0U;
|
|
||||||
reply[2U] = MMDVM_SAMPLES;
|
|
||||||
|
|
||||||
reply[5U] = mode;
|
|
||||||
reply[6U] = control;
|
|
||||||
|
|
||||||
uint16_t count = 7U;
|
|
||||||
for (uint16_t i = 0U; i < nSamples; i++) {
|
|
||||||
uint16_t val = uint16_t(samples[i] + 2048);
|
|
||||||
|
|
||||||
reply[count++] = (val >> 8) & 0xFF;
|
|
||||||
reply[count++] = (val >> 0) & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
reply[3U] = (count >> 8) & 0xFFU;
|
|
||||||
reply[4U] = (count >> 0) & 0xFFU;
|
|
||||||
|
|
||||||
writeInt(1U, reply, count, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSerialPort::writeDebug(const char* text)
|
void CSerialPort::writeDebug(const char* text)
|
||||||
{
|
{
|
||||||
|
if (!m_debug)
|
||||||
|
return;
|
||||||
|
|
||||||
uint8_t reply[130U];
|
uint8_t reply[130U];
|
||||||
|
|
||||||
reply[0U] = MMDVM_FRAME_START;
|
reply[0U] = MMDVM_FRAME_START;
|
||||||
|
@ -964,6 +952,9 @@ void CSerialPort::writeDebug(const char* text)
|
||||||
|
|
||||||
void CSerialPort::writeDebug(const char* text, int16_t n1)
|
void CSerialPort::writeDebug(const char* text, int16_t n1)
|
||||||
{
|
{
|
||||||
|
if (!m_debug)
|
||||||
|
return;
|
||||||
|
|
||||||
uint8_t reply[130U];
|
uint8_t reply[130U];
|
||||||
|
|
||||||
reply[0U] = MMDVM_FRAME_START;
|
reply[0U] = MMDVM_FRAME_START;
|
||||||
|
@ -984,6 +975,9 @@ void CSerialPort::writeDebug(const char* text, int16_t n1)
|
||||||
|
|
||||||
void CSerialPort::writeDebug(const char* text, int16_t n1, int16_t n2)
|
void CSerialPort::writeDebug(const char* text, int16_t n1, int16_t n2)
|
||||||
{
|
{
|
||||||
|
if (!m_debug)
|
||||||
|
return;
|
||||||
|
|
||||||
uint8_t reply[130U];
|
uint8_t reply[130U];
|
||||||
|
|
||||||
reply[0U] = MMDVM_FRAME_START;
|
reply[0U] = MMDVM_FRAME_START;
|
||||||
|
@ -1007,6 +1001,9 @@ void CSerialPort::writeDebug(const char* text, int16_t n1, int16_t n2)
|
||||||
|
|
||||||
void CSerialPort::writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n3)
|
void CSerialPort::writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n3)
|
||||||
{
|
{
|
||||||
|
if (!m_debug)
|
||||||
|
return;
|
||||||
|
|
||||||
uint8_t reply[130U];
|
uint8_t reply[130U];
|
||||||
|
|
||||||
reply[0U] = MMDVM_FRAME_START;
|
reply[0U] = MMDVM_FRAME_START;
|
||||||
|
@ -1033,6 +1030,9 @@ void CSerialPort::writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n
|
||||||
|
|
||||||
void CSerialPort::writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n3, int16_t n4)
|
void CSerialPort::writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n3, int16_t n4)
|
||||||
{
|
{
|
||||||
|
if (!m_debug)
|
||||||
|
return;
|
||||||
|
|
||||||
uint8_t reply[130U];
|
uint8_t reply[130U];
|
||||||
|
|
||||||
reply[0U] = MMDVM_FRAME_START;
|
reply[0U] = MMDVM_FRAME_START;
|
||||||
|
|
|
@ -49,8 +49,6 @@ public:
|
||||||
void writeCalData(const uint8_t* data, uint8_t length);
|
void writeCalData(const uint8_t* data, uint8_t length);
|
||||||
void writeRSSIData(const uint8_t* data, uint8_t length);
|
void writeRSSIData(const uint8_t* data, uint8_t length);
|
||||||
|
|
||||||
void writeSamples(uint8_t mode, uint8_t control, const q15_t* samples, uint16_t nSamples);
|
|
||||||
|
|
||||||
void writeDebug(const char* text);
|
void writeDebug(const char* text);
|
||||||
void writeDebug(const char* text, int16_t n1);
|
void writeDebug(const char* text, int16_t n1);
|
||||||
void writeDebug(const char* text, int16_t n1, int16_t n2);
|
void writeDebug(const char* text, int16_t n1, int16_t n2);
|
||||||
|
@ -63,6 +61,7 @@ private:
|
||||||
uint8_t m_buffer[256U];
|
uint8_t m_buffer[256U];
|
||||||
uint8_t m_ptr;
|
uint8_t m_ptr;
|
||||||
uint8_t m_len;
|
uint8_t m_len;
|
||||||
|
bool m_debug;
|
||||||
|
|
||||||
void sendACK();
|
void sendACK();
|
||||||
void sendNAK(uint8_t err);
|
void sendNAK(uint8_t err);
|
||||||
|
|
24
YSFRX.cpp
24
YSFRX.cpp
|
@ -16,10 +16,6 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define WANT_DEBUG
|
|
||||||
|
|
||||||
// #define DUMP_SAMPLES
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "YSFRX.h"
|
#include "YSFRX.h"
|
||||||
|
@ -201,9 +197,6 @@ void CYSFRX::processData(q15_t sample)
|
||||||
} else {
|
} else {
|
||||||
frame[0U] = m_lostCount == (MAX_SYNC_FRAMES - 1U) ? 0x01U : 0x00U;
|
frame[0U] = m_lostCount == (MAX_SYNC_FRAMES - 1U) ? 0x01U : 0x00U;
|
||||||
writeRSSIData(frame);
|
writeRSSIData(frame);
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
writeSamples(m_startPtr, frame[0U]);
|
|
||||||
#endif
|
|
||||||
m_maxCorr = 0;
|
m_maxCorr = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,20 +402,3 @@ void CYSFRX::writeRSSIData(uint8_t* data)
|
||||||
m_rssiAccum = 0U;
|
m_rssiAccum = 0U;
|
||||||
m_rssiCount = 0U;
|
m_rssiCount = 0U;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(DUMP_SAMPLES)
|
|
||||||
void CYSFRX::writeSamples(uint16_t start, uint8_t control)
|
|
||||||
{
|
|
||||||
q15_t samples[YSF_FRAME_LENGTH_SYMBOLS];
|
|
||||||
|
|
||||||
for (uint16_t i = 0U; i < YSF_FRAME_LENGTH_SYMBOLS; i++) {
|
|
||||||
samples[i] = m_buffer[start];
|
|
||||||
|
|
||||||
start += YSF_RADIO_SYMBOL_LENGTH;
|
|
||||||
if (start >= YSF_FRAME_LENGTH_SAMPLES)
|
|
||||||
start -= YSF_FRAME_LENGTH_SAMPLES;
|
|
||||||
}
|
|
||||||
|
|
||||||
serial.writeSamples(STATE_YSF, control, samples, YSF_FRAME_LENGTH_SYMBOLS);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
1
YSFRX.h
1
YSFRX.h
|
@ -63,7 +63,6 @@ private:
|
||||||
void calculateLevels(uint16_t start, uint16_t count);
|
void calculateLevels(uint16_t start, uint16_t count);
|
||||||
void samplesToBits(uint16_t start, uint16_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold);
|
void samplesToBits(uint16_t start, uint16_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold);
|
||||||
void writeRSSIData(uint8_t* data);
|
void writeRSSIData(uint8_t* data);
|
||||||
void writeSamples(uint16_t start, uint8_t control);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
18
YSFTX.cpp
18
YSFTX.cpp
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
* Copyright (C) 2009-2017 by Jonathan Naylor G4KLX
|
||||||
|
* Copyright (C) 2017 by Andy Uribe CA6JAU
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,26 +17,17 @@
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// #define WANT_DEBUG
|
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "YSFTX.h"
|
#include "YSFTX.h"
|
||||||
|
|
||||||
#include "YSFDefines.h"
|
#include "YSFDefines.h"
|
||||||
|
|
||||||
#if defined(WIDE_C4FSK_FILTERS_TX)
|
|
||||||
// Generated using rcosdesign(0.2, 4, 5, 'sqrt') in MATLAB
|
|
||||||
static q15_t YSF_C4FSK_FILTER[] = {0, 0, 0, 0, 688, -680, -2158, -3060, -2724, -775, 2684, 7041, 11310, 14425, 15565, 14425,
|
|
||||||
11310, 7041, 2684, -775, -2724, -3060, -2158, -680, 688}; // numTaps = 25, L = 5
|
|
||||||
const uint16_t YSF_C4FSK_FILTER_PHASE_LEN = 5U; // phaseLength = numTaps/L
|
|
||||||
#else
|
|
||||||
// Generated using rcosdesign(0.2, 8, 5, 'sqrt') in MATLAB
|
// Generated using rcosdesign(0.2, 8, 5, 'sqrt') in MATLAB
|
||||||
static q15_t YSF_C4FSK_FILTER[] = {0, 0, 0, 0, 401, 104, -340, -731, -847, -553, 112, 909, 1472, 1450, 683, -675, -2144, -3040, -2706, -770, 2667, 6995,
|
static q15_t RRC_0_2_FILTER[] = {0, 0, 0, 0, 401, 104, -340, -731, -847, -553, 112, 909, 1472, 1450, 683, -675, -2144, -3040, -2706, -770, 2667, 6995,
|
||||||
11237, 14331, 15464, 14331, 11237, 6995, 2667, -770, -2706, -3040, -2144, -675, 683, 1450, 1472, 909, 112,
|
11237, 14331, 15464, 14331, 11237, 6995, 2667, -770, -2706, -3040, -2144, -675, 683, 1450, 1472, 909, 112,
|
||||||
-553, -847, -731, -340, 104, 401}; // numTaps = 45, L = 5
|
-553, -847, -731, -340, 104, 401}; // numTaps = 45, L = 5
|
||||||
const uint16_t YSF_C4FSK_FILTER_PHASE_LEN = 9U; // phaseLength = numTaps/L
|
const uint16_t RRC_0_2_FILTER_PHASE_LEN = 9U; // phaseLength = numTaps/L
|
||||||
#endif
|
|
||||||
|
|
||||||
const q15_t YSF_LEVELA_HI = 3900;
|
const q15_t YSF_LEVELA_HI = 3900;
|
||||||
const q15_t YSF_LEVELB_HI = 1300;
|
const q15_t YSF_LEVELB_HI = 1300;
|
||||||
|
@ -63,8 +55,8 @@ m_loDev(false)
|
||||||
::memset(m_modState, 0x00U, 16U * sizeof(q15_t));
|
::memset(m_modState, 0x00U, 16U * sizeof(q15_t));
|
||||||
|
|
||||||
m_modFilter.L = YSF_RADIO_SYMBOL_LENGTH;
|
m_modFilter.L = YSF_RADIO_SYMBOL_LENGTH;
|
||||||
m_modFilter.phaseLength = YSF_C4FSK_FILTER_PHASE_LEN;
|
m_modFilter.phaseLength = RRC_0_2_FILTER_PHASE_LEN;
|
||||||
m_modFilter.pCoeffs = YSF_C4FSK_FILTER;
|
m_modFilter.pCoeffs = RRC_0_2_FILTER;
|
||||||
m_modFilter.pState = m_modState;
|
m_modFilter.pState = m_modState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue