|
|
|
@ -28,7 +28,10 @@
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <strings.h>
|
|
|
|
|
#include <pthread.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include "srslte/srslte.h"
|
|
|
|
|
#include "srslte/cuhd/cuhd.h"
|
|
|
|
|
|
|
|
|
|
#include "srslte/ue_itf/phy.h"
|
|
|
|
|
#include "srslte/ue_itf/prach.h"
|
|
|
|
@ -38,28 +41,38 @@
|
|
|
|
|
namespace srslte {
|
|
|
|
|
namespace ue {
|
|
|
|
|
|
|
|
|
|
#if SYNC_MODE==SYNC_MODE_CALLBACK
|
|
|
|
|
phy::phy(ue_phy_callback_t tti_clock_callback_, ue_phy_callback_t status_change_)
|
|
|
|
|
|
|
|
|
|
bool phy::init_radio_handler(char *args) {
|
|
|
|
|
printf("Opening UHD device...\n");
|
|
|
|
|
if (cuhd_open(args, &radio_handler)) {
|
|
|
|
|
fprintf(stderr, "Error opening uhd\n");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool phy::init(ue_phy_callback_tti_t tti_clock_callback_, ue_phy_callback_status_t status_change_)
|
|
|
|
|
{
|
|
|
|
|
started = false;
|
|
|
|
|
tti_clock_callback = tti_clock_callback_;
|
|
|
|
|
status_change = status_change_;
|
|
|
|
|
ul_buffer_queue = new queue(6, sizeof(ul_buffer));
|
|
|
|
|
dl_buffer_queue = new queue(6, sizeof(dl_buffer));
|
|
|
|
|
|
|
|
|
|
started = true;
|
|
|
|
|
// Set default params
|
|
|
|
|
params_db.set_param(params::CELLSEARCH_TIMEOUT_PSS_NFRAMES, 100);
|
|
|
|
|
params_db.set_param(params::CELLSEARCH_TIMEOUT_PSS_CORRELATION_THRESHOLD, 160);
|
|
|
|
|
params_db.set_param(params::CELLSEARCH_TIMEOUT_MIB_NFRAMES, 100);
|
|
|
|
|
|
|
|
|
|
pthread_create(&radio_thread, NULL, radio_thread_fnc, this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
phy()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (init_radio_handler("")) {
|
|
|
|
|
pthread_create(&radio_thread, NULL, radio_thread_fnc, this);
|
|
|
|
|
started = true;
|
|
|
|
|
}
|
|
|
|
|
return started;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
phy::~phy()
|
|
|
|
|
void phy::stop()
|
|
|
|
|
{
|
|
|
|
|
started = false;
|
|
|
|
|
|
|
|
|
@ -76,9 +89,33 @@ phy::~phy()
|
|
|
|
|
prach_buffer.free_cell();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void phy::set_tx_gain(float gain) {
|
|
|
|
|
float x = cuhd_set_tx_gain(radio_handler, gain);
|
|
|
|
|
printf("Set TX gain to %.1f dB\n", x);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void phy::set_rx_gain(float gain) {
|
|
|
|
|
float x = cuhd_set_rx_gain(radio_handler, gain);
|
|
|
|
|
printf("Set RX gain to %.1f dB\n", x);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void phy::set_tx_freq(float freq) {
|
|
|
|
|
float x = cuhd_set_tx_freq(radio_handler, freq);
|
|
|
|
|
printf("Set TX freq to %.1f MHz\n", x/1000000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void phy::set_rx_freq(float freq) {
|
|
|
|
|
float x = cuhd_set_rx_freq(radio_handler, freq);
|
|
|
|
|
printf("Set RX freq to %.1f MHz\n", x/1000000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void phy::set_param(params::param_t param, int64_t value) {
|
|
|
|
|
params_db.set_param(param, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// FIXME: Add PRACH power control
|
|
|
|
|
void phy::send_prach(uint32_t preamble_idx)
|
|
|
|
|
bool phy::send_prach(uint32_t preamble_idx)
|
|
|
|
|
{
|
|
|
|
|
if (phy_state == RXTX) {
|
|
|
|
|
prach_buffer.ready_to_send(preamble_idx);
|
|
|
|
@ -86,39 +123,50 @@ void phy::send_prach(uint32_t preamble_idx)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Do fast measurement on RSSI and/or PSS autocorrelation energy or PSR
|
|
|
|
|
void phy::measure()
|
|
|
|
|
bool phy::measure()
|
|
|
|
|
{
|
|
|
|
|
if (phy_state == IDLE) {
|
|
|
|
|
// capture and do measurement
|
|
|
|
|
status_change();
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void phy::dl_bch()
|
|
|
|
|
bool phy::start_rxtx()
|
|
|
|
|
{
|
|
|
|
|
if (phy_state == IDLE) {
|
|
|
|
|
phy_state = RX_BCH;
|
|
|
|
|
status_change();
|
|
|
|
|
}
|
|
|
|
|
if (cell_is_set) {
|
|
|
|
|
// Set RX/TX sampling rate
|
|
|
|
|
cuhd_set_rx_srate(radio_handler, srslte_sampling_freq_hz(cell.nof_prb));
|
|
|
|
|
cuhd_set_tx_srate(radio_handler, srslte_sampling_freq_hz(cell.nof_prb));
|
|
|
|
|
|
|
|
|
|
// Start streaming
|
|
|
|
|
cuhd_start_rx_stream(radio_handler);
|
|
|
|
|
phy_state = RXTX;
|
|
|
|
|
status_change();
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(stderr, "Can not change state to RXTX: cell is not set\n");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(stderr, "Can not change state to RXTX: invalid state %d\n", phy_state);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void phy::start_rxtx()
|
|
|
|
|
bool phy::stop_rxtx()
|
|
|
|
|
{
|
|
|
|
|
if (phy_state == MIB_DECODED) {
|
|
|
|
|
// Set sampling freq to MIB PRB
|
|
|
|
|
// start radio streaming
|
|
|
|
|
phy_state = RXTX;
|
|
|
|
|
if (phy_state == RXTX) {
|
|
|
|
|
// Stop streaming
|
|
|
|
|
cuhd_stop_rx_stream(radio_handler);
|
|
|
|
|
phy_state = IDLE;
|
|
|
|
|
status_change();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(stderr, "Can not change state to RXTX: invalid state %d\n", phy_state);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void phy::stop_rxtx()
|
|
|
|
|
{
|
|
|
|
|
// stop radio
|
|
|
|
|
phy_state = IDLE;
|
|
|
|
|
status_change();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool phy::status_is_idle() {
|
|
|
|
|
return phy_state == IDLE;
|
|
|
|
|
}
|
|
|
|
@ -127,13 +175,16 @@ bool phy::status_is_rxtx() {
|
|
|
|
|
return phy_state == RXTX;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool phy::status_is_bch_decoded(uint8_t payload[SRSLTE_BCH_PAYLOAD_LEN])
|
|
|
|
|
{
|
|
|
|
|
if (phy_state == MIB_DECODED) {
|
|
|
|
|
memcpy(payload, bch_payload, SRSLTE_BCH_PAYLOAD_LEN*sizeof(uint8_t));
|
|
|
|
|
}
|
|
|
|
|
uint32_t phy::get_current_tti() {
|
|
|
|
|
return current_tti;
|
|
|
|
|
}
|
|
|
|
|
uint32_t phy::tti_to_SFN(uint32_t tti) {
|
|
|
|
|
return tti/10;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t phy::tti_to_subf(uint32_t tti) {
|
|
|
|
|
return tti%10;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void* phy::radio_thread_fnc(void *arg) {
|
|
|
|
|
phy* phy = static_cast<srslte::ue::phy*>(arg);
|
|
|
|
@ -141,46 +192,37 @@ void* phy::radio_thread_fnc(void *arg) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int radio_recv_wrapper_cs(void*,void*,uint32_t,srslte_timestamp_t*)
|
|
|
|
|
int radio_recv_wrapper_cs(void *h,void *data, uint32_t nsamples, srslte_timestamp_t*)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
return cuhd_recv(h, data, nsamples, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void phy::run_rx_bch_state() {
|
|
|
|
|
phy_state = IDLE;
|
|
|
|
|
if (rx_bch()) {
|
|
|
|
|
for(uint32_t i=0;i<6;i++) {
|
|
|
|
|
get_ul_buffer(i)->init_cell(cell, ¶ms_db);
|
|
|
|
|
get_dl_buffer(i)->init_cell(cell, ¶ms_db);
|
|
|
|
|
}
|
|
|
|
|
if (srslte_ue_mib_init(&ue_mib, cell)) {
|
|
|
|
|
fprintf(stderr, "Error initiating UE mib\n");
|
|
|
|
|
} else {
|
|
|
|
|
if (srslte_ue_sync_init(&ue_sync, cell, radio_recv_wrapper_cs, radio_handler)) {
|
|
|
|
|
fprintf(stderr, "Error initiating ue_sync");
|
|
|
|
|
bool phy::set_cell(srslte_cell_t cell_) {
|
|
|
|
|
if (phy_state == IDLE) {
|
|
|
|
|
cell_is_set = false;
|
|
|
|
|
cell = cell_;
|
|
|
|
|
if (!srslte_ue_mib_init(&ue_mib, cell))
|
|
|
|
|
{
|
|
|
|
|
if (!srslte_ue_sync_init(&ue_sync, cell, radio_recv_wrapper_cs, radio_handler))
|
|
|
|
|
{
|
|
|
|
|
if (prach_buffer.init_cell(cell, ¶ms_db)) {
|
|
|
|
|
for(uint32_t i=0;i<6;i++) {
|
|
|
|
|
get_ul_buffer(i)->init_cell(cell, ¶ms_db);
|
|
|
|
|
get_dl_buffer(i)->init_cell(cell, ¶ms_db);
|
|
|
|
|
get_dl_buffer(i)->buffer_id = i;
|
|
|
|
|
}
|
|
|
|
|
cell_is_set = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
phy_state = MIB_DECODED;
|
|
|
|
|
fprintf(stderr, "Error setting cell: initiating ue_sync");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(stderr, "Error setting cell: initiating ue_mib\n");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(stderr, "Error setting cell: Invalid state %d\n", phy_state);
|
|
|
|
|
}
|
|
|
|
|
status_change();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void phy::main_radio_loop() {
|
|
|
|
|
while(started) {
|
|
|
|
|
switch(phy_state) {
|
|
|
|
|
case IDLE:
|
|
|
|
|
case MIB_DECODED:
|
|
|
|
|
break;
|
|
|
|
|
case RX_BCH:
|
|
|
|
|
run_rx_bch_state();
|
|
|
|
|
break;
|
|
|
|
|
case RXTX:
|
|
|
|
|
run_rx_tx_state();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return cell_is_set;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ul_buffer* phy::get_ul_buffer(uint32_t tti)
|
|
|
|
@ -193,7 +235,16 @@ dl_buffer* phy::get_dl_buffer(uint32_t tti)
|
|
|
|
|
return (dl_buffer*) dl_buffer_queue->get(tti);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool phy::rx_bch()
|
|
|
|
|
|
|
|
|
|
bool phy::decode_mib(uint32_t N_id_2, srslte_cell_t *cell, uint8_t payload[SRSLTE_BCH_PAYLOAD_LEN]) {
|
|
|
|
|
return decode_mib_N_id_2((int) N_id_2, cell, payload);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool phy::decode_mib_best(srslte_cell_t *cell, uint8_t payload[SRSLTE_BCH_PAYLOAD_LEN]) {
|
|
|
|
|
return decode_mib_N_id_2(-1, cell, payload);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool phy::decode_mib_N_id_2(int force_N_id_2, srslte_cell_t *cell_ptr, uint8_t bch_payload[SRSLTE_BCH_PAYLOAD_LEN])
|
|
|
|
|
{
|
|
|
|
|
srslte_ue_cellsearch_result_t found_cells[3];
|
|
|
|
|
srslte_ue_cellsearch_t cs;
|
|
|
|
@ -205,67 +256,60 @@ bool phy::rx_bch()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
srslte_ue_cellsearch_set_nof_frames_to_scan(&cs, params_db.get_param(params::CELLSEARCH_TIMEOUT_PSS_NFRAMES));
|
|
|
|
|
srslte_ue_cellsearch_set_threshold(&cs, (float) params_db.get_param(params::CELLSEARCH_CORRELATION_THRESHOLD)/10);
|
|
|
|
|
srslte_ue_cellsearch_set_threshold(&cs, (float) params_db.get_param(params::CELLSEARCH_TIMEOUT_PSS_CORRELATION_THRESHOLD)/10);
|
|
|
|
|
|
|
|
|
|
// set sampling freq 1.92 MHz
|
|
|
|
|
// set frequency, gain etc
|
|
|
|
|
// start radio streaming
|
|
|
|
|
cuhd_set_rx_srate(radio_handler, 1920000.0);
|
|
|
|
|
cuhd_start_rx_stream(radio_handler);
|
|
|
|
|
|
|
|
|
|
/* Find a cell in the given N_id_2 or go through the 3 of them to find the strongest */
|
|
|
|
|
uint32_t max_peak_cell = 0;
|
|
|
|
|
int ret = SRSLTE_ERROR;
|
|
|
|
|
uint32_t force_N_id_2 = params_db.get_param(params::CELLSEARCH_FORCE_N_ID_2);
|
|
|
|
|
if (force_N_id_2 >= 0 && force_N_id_2 < 3) {
|
|
|
|
|
ret = srslte_ue_cellsearch_scan_N_id_2(&cs, force_N_id_2, &found_cells[force_N_id_2]);
|
|
|
|
|
max_peak_cell = force_N_id_2;
|
|
|
|
|
} else {
|
|
|
|
|
ret = srslte_ue_cellsearch_scan(&cs, found_cells, &max_peak_cell);
|
|
|
|
|
}
|
|
|
|
|
// Stop radio
|
|
|
|
|
|
|
|
|
|
cuhd_stop_rx_stream(radio_handler);
|
|
|
|
|
srslte_ue_cellsearch_free(&cs);
|
|
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
fprintf(stderr, "Error searching cell");
|
|
|
|
|
fprintf(stderr, "Error decoding MIB: Error searching PSS\n");
|
|
|
|
|
return false;
|
|
|
|
|
} else if (ret == 0) {
|
|
|
|
|
fprintf(stderr, "Could not find any cell in this frequency");
|
|
|
|
|
fprintf(stderr, "Error decoding MIB: Could not find any PSS in this frequency\n");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save result
|
|
|
|
|
cell.id = found_cells[max_peak_cell].cell_id;
|
|
|
|
|
cell.cp = found_cells[max_peak_cell].cp;
|
|
|
|
|
cell_ptr->id = found_cells[max_peak_cell].cell_id;
|
|
|
|
|
cell_ptr->cp = found_cells[max_peak_cell].cp;
|
|
|
|
|
|
|
|
|
|
printf("Found CELL PHY_ID: %d, CP: %s PSR: %.1f AbsPower: %.1f dBm",
|
|
|
|
|
cell.id, srslte_cp_string(cell.cp),
|
|
|
|
|
found_cells[max_peak_cell].psr, 30+10*log10(found_cells[max_peak_cell].peak));
|
|
|
|
|
INFO("\nFound CELL ID: %d CP: %s\n", cell_ptr->id, srslte_cp_string(cell_ptr->cp));
|
|
|
|
|
|
|
|
|
|
srslte_ue_mib_sync_t ue_mib_sync;
|
|
|
|
|
|
|
|
|
|
if (srslte_ue_mib_sync_init(&ue_mib_sync, cell.id, cell.cp, radio_recv_wrapper_cs, radio_handler)) {
|
|
|
|
|
if (srslte_ue_mib_sync_init(&ue_mib_sync, cell_ptr->id, cell_ptr->cp, radio_recv_wrapper_cs, radio_handler)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t sfn, sfn_offset;
|
|
|
|
|
|
|
|
|
|
/* Find and decode MIB */
|
|
|
|
|
uint32_t sfn, sfn_offset;
|
|
|
|
|
|
|
|
|
|
// Start RX stream again
|
|
|
|
|
|
|
|
|
|
cuhd_start_rx_stream(radio_handler);
|
|
|
|
|
ret = srslte_ue_mib_sync_decode(&ue_mib_sync, params_db.get_param(params::CELLSEARCH_TIMEOUT_MIB_NFRAMES),
|
|
|
|
|
bch_payload, &cell.nof_ports, &sfn_offset);
|
|
|
|
|
// Stop RX stream again
|
|
|
|
|
bch_payload, &cell_ptr->nof_ports, &sfn_offset);
|
|
|
|
|
cuhd_stop_rx_stream(radio_handler);
|
|
|
|
|
srslte_ue_mib_sync_free(&ue_mib_sync);
|
|
|
|
|
|
|
|
|
|
if (ret == 1) {
|
|
|
|
|
srslte_pbch_mib_unpack(bch_payload, &cell, &sfn);
|
|
|
|
|
srslte_pbch_mib_unpack(bch_payload, cell_ptr, &sfn);
|
|
|
|
|
sfn = (sfn + sfn_offset)%1024;
|
|
|
|
|
current_tti = sfn*10+1;
|
|
|
|
|
printf("MIB decoded: %d ports, SFN: %d, TTI: %d", cell.nof_ports, sfn, current_tti);
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
printf("Error decoding MIB");
|
|
|
|
|
printf("Error decoding MIB: Error decoding PBCH\n");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -275,6 +319,7 @@ int phy::sync_sfn(void) {
|
|
|
|
|
|
|
|
|
|
cf_t *sf_buffer = NULL;
|
|
|
|
|
int ret = SRSLTE_ERROR;
|
|
|
|
|
uint8_t bch_payload[SRSLTE_BCH_PAYLOAD_LEN];
|
|
|
|
|
|
|
|
|
|
ret = srslte_ue_sync_get_buffer(&ue_sync, &sf_buffer);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
@ -295,7 +340,7 @@ int phy::sync_sfn(void) {
|
|
|
|
|
srslte_pbch_mib_unpack(bch_payload, &cell, &sfn);
|
|
|
|
|
|
|
|
|
|
sfn = (sfn + sfn_offset)%1024;
|
|
|
|
|
current_tti = sfn*10;
|
|
|
|
|
current_tti = sfn*10 + 1;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -308,12 +353,14 @@ void phy::run_rx_tx_state()
|
|
|
|
|
{
|
|
|
|
|
int ret;
|
|
|
|
|
if (!is_sfn_synched) {
|
|
|
|
|
printf("Synchronising SFN...\n");
|
|
|
|
|
ret = sync_sfn();
|
|
|
|
|
switch(ret) {
|
|
|
|
|
default:
|
|
|
|
|
phy_state = IDLE;
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
printf("SFN synched ok\n");
|
|
|
|
|
is_sfn_synched = true;
|
|
|
|
|
break;
|
|
|
|
|
case 0:
|
|
|
|
@ -322,7 +369,7 @@ void phy::run_rx_tx_state()
|
|
|
|
|
} else {
|
|
|
|
|
// Receive alligned buffer for the current tti
|
|
|
|
|
srslte_timestamp_t rx_time;
|
|
|
|
|
get_dl_buffer(current_tti)->recv_ue_sync(&ue_sync, &rx_time);
|
|
|
|
|
get_dl_buffer(current_tti)->recv_ue_sync(current_tti, &ue_sync, &rx_time);
|
|
|
|
|
|
|
|
|
|
// send prach if we have to
|
|
|
|
|
if (prach_buffer.is_ready_to_send(current_tti)) {
|
|
|
|
@ -332,10 +379,28 @@ void phy::run_rx_tx_state()
|
|
|
|
|
if (get_ul_buffer(current_tti)->is_ready_to_send()) {
|
|
|
|
|
get_ul_buffer(current_tti)->send_packet(radio_handler, rx_time);
|
|
|
|
|
}
|
|
|
|
|
tti_clock_callback(current_tti);
|
|
|
|
|
current_tti = (current_tti + 1)%10240;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void phy::main_radio_loop() {
|
|
|
|
|
printf("PHY initiated\n");
|
|
|
|
|
|
|
|
|
|
while(started) {
|
|
|
|
|
switch(phy_state) {
|
|
|
|
|
case IDLE:
|
|
|
|
|
usleep(50000);
|
|
|
|
|
break;
|
|
|
|
|
case RXTX:
|
|
|
|
|
printf("Run RX_TX\n");
|
|
|
|
|
run_rx_tx_state();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
current_tti++;
|
|
|
|
|
tti_clock_callback();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|