From ddd41921b4dbcd22c1e4bcfa92fc86acdaaca977 Mon Sep 17 00:00:00 2001 From: Xavier Date: Sun, 4 Oct 2026 19:33:21 +0200 Subject: [PATCH] =?UTF-8?q?T=C3=A9l=C3=A9verser=20les=20fichiers=20vers=20?= =?UTF-8?q?"src"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cibi.cpp | 574 ++++++++++++++++++++++++++++++++++++++++++ src/cibidisplay.cpp | 303 ++++++++++++++++++++++ src/config.cpp | 307 ++++++++++++++++++++++ src/configdisplay.cpp | 153 +++++++++++ src/dds.cpp | 100 ++++++++ 5 files changed, 1437 insertions(+) create mode 100644 src/cibi.cpp create mode 100644 src/cibidisplay.cpp create mode 100644 src/config.cpp create mode 100644 src/configdisplay.cpp create mode 100644 src/dds.cpp diff --git a/src/cibi.cpp b/src/cibi.cpp new file mode 100644 index 0000000..3da0d7d --- /dev/null +++ b/src/cibi.cpp @@ -0,0 +1,574 @@ +/* + * Copyright (c) 2019, Vincent Hervieux vincent.hervieux@gmail.com + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the author Vincent Hervieux, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cibi.h" +#include "utils.h" +#include "vfo.h" + +const int8_t Cibi::cibi_channels[45] PROGMEM = { + 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 9, + 10, 11, 11, 12, 13, 14, 15, 15, 16, 17, 18, 19, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40}; + +const bool Cibi::cibi_channels_bis[45] PROGMEM = { + /* 1- 9*/ false, false, false, true /*3*/, false, false, false, false, true /*7*/, false, false, + /*10-19*/ false, false, true /*11*/, false, false, false, false, true /*15*/, false, false, false, false, true /*19*/, + /*20-29*/ false, false, false, false, false, false, false, false, false, false, + /*30-39*/ false, false, false, false, false, false, false, false, false, false, + /* 40 */ false}; + +Cibi::Cibi(Config &_config, Input &_input, U8G2 &_display, DDS &_dds) : config_(_config), + input_(_input), + dds_(_dds), + current_cibi_channel_({.channel = -1, + .band = CIBI_BAND_UNKNOWN, + .bis = false}), + current_freq_(0), + freq_change_time_(0), + current_clarifier_(0), + freq_step_(0), + current_cursor_pos_(CURSOR_POS_FREQ_DIG5), // <-- should be a parameter? + current_modulation_(-1), + current_smeter_(0), + current_tx_(false), + scanning_(false) +{ + view_ = new CibiDisplay(_display); + updateFreqStep(current_cursor_pos_); +} + +Cibi::Cibi(const Cibi &_cibi) : config_(_cibi.config_), + input_(_cibi.input_), + dds_(_cibi.dds_), + current_cibi_channel_({.channel = _cibi.current_cibi_channel_.channel, + .band = _cibi.current_cibi_channel_.band, + .bis = _cibi.current_cibi_channel_.bis}), + current_freq_(_cibi.current_freq_), + freq_change_time_(_cibi.freq_change_time_), + current_clarifier_(_cibi.current_clarifier_), + freq_step_(_cibi.freq_step_), + current_cursor_pos_(_cibi.current_cursor_pos_), + current_modulation_(_cibi.current_modulation_), + current_smeter_(_cibi.current_smeter_), + current_tx_(_cibi.current_tx_), + scanning_(_cibi.scanning_), + view_(_cibi.view_) +{ +} + +Cibi::~Cibi() +{ + delete view_; +} + +int Cibi::getCibiChannel(uint32_t _frequency, Cibi::cibi_channel_t *_channel) +{ + /* Cibi channels */ + /* Pay attention that TX over 28Mhz is reserved for HAM use */ + const static uint16_t FREQ_CIBI_LOW_DAKHZ PROGMEM = 2561; + const static uint16_t FREQ_CIBI_HIGH_DAKHZ PROGMEM = 2875; + /* A cibi band is 45 channels: + - 40 channels + - 5 'bis' channels: 3,7,11,15,19 */ + const uint16_t CIBI_BAND_DAKHZ PROGMEM = 45; + /* Round to kHz to compute channels + * This avoids to get no display of channels + * in case a fine tuning was made to clarify the voice + */ + uint16_t freq_dakhz = _frequency / 10000; + _channel->channel = -1; + _channel->band = CIBI_BAND_UNKNOWN; + _channel->bis = false; + /* Check if we are not out of Cibi band */ + if (freq_dakhz < FREQ_CIBI_LOW_DAKHZ || freq_dakhz > FREQ_CIBI_HIGH_DAKHZ) + return -1; + + /* move down band to 0 for computation */ + freq_dakhz -= FREQ_CIBI_LOW_DAKHZ; + + _channel->band = freq_dakhz / CIBI_BAND_DAKHZ; + /* reuse variable freq_dakhz as index in cibi_channel array, */ + freq_dakhz = freq_dakhz % CIBI_BAND_DAKHZ; + _channel->channel = pgm_read_byte_near(cibi_channels + freq_dakhz); + _channel->bis = pgm_read_byte_near(cibi_channels_bis + freq_dakhz); + return 0; +} + +void Cibi::init() +{ + if (0 == current_freq_) + { + /* startup: retrieve value from config and compute our context */ + current_freq_ = config_.getCibiFreq(); + current_modulation_ = input_.getModulation(); + current_tx_ = input_.getTx(); + /* update DDS according to current_freq_ and current_modulation_*/ + updateDDS(current_freq_, current_modulation_, current_tx_); + } + /* init display with newly created context */ + view_->init(); + view_->setFreq(current_freq_, WHITE); + bool cibiBand = (0 == getCibiChannel(current_freq_, ¤t_cibi_channel_)); + if(cibiBand) + { + view_->setCibiChannel(current_cibi_channel_.channel, WHITE); + view_->setCibiBand(current_cibi_channel_.band, WHITE); + view_->setCibiChannelBis(current_cibi_channel_.bis, WHITE); + } + else + { + /* HAM band */ + view_->setBand(current_freq_, WHITE); + } + view_->setCursorPos(current_cursor_pos_, WHITE); + view_->setModulation(current_modulation_, WHITE); +#ifdef SMETER_DISPLAY + view_->setSMeter(0); +#endif +} + +#if 1 +void Cibi::updateDisplay(uint32_t _frequency, int _position, int _modulation, bool _tx, int _smeter) +{ + cibi_channel_t cibi_channel({.channel = -1, + .band = CIBI_BAND_UNKNOWN, + .bis = false}); + uint32_t clarified_freq = add_int32_to_uint32(current_freq_, current_clarifier_); + if (_frequency != clarified_freq || + current_cibi_channel_.channel != cibi_channel.channel || + _position != current_cursor_pos_ || + _modulation != current_modulation_ || + _tx != current_tx_) + { + view_->clear(); + /* TODO: could be optimized to update only the impacted digits */ + view_->setTx(_tx); + view_->setFreq(_frequency, (_tx? BLACK : WHITE)); + /* if withing the 10-11m band, check for Cibi channels */ + /* Outside this range, set HAM bands */ + bool cibiBand = (0 == getCibiChannel(_frequency, &cibi_channel)); + if(cibiBand) + { +#if 0 // def DEBUG + Serial.println(cibi_channel.channel); +#endif // DEBUG + view_->setCibiChannel(cibi_channel.channel, WHITE); + current_cibi_channel_.channel = cibi_channel.channel; + + view_->setCibiBand(cibi_channel.band, WHITE); + current_cibi_channel_.band = cibi_channel.band; + + view_->setCibiChannelBis(cibi_channel.bis, WHITE); + current_cibi_channel_.bis = cibi_channel.bis; + } + else + { + view_->setBand(_frequency, WHITE); + current_cibi_channel_.channel = -1; + current_cibi_channel_.band = CIBI_BAND_UNKNOWN; + } + + view_->setCursorPos(_position, WHITE); + view_->setModulation(_modulation, WHITE); +#ifdef SMETER_DISPLAY + view_->setSMeter(_smeter); +#else + view_->display(); +#endif + clarified_freq = _frequency; + current_cibi_channel_.channel = cibi_channel.channel; + current_cursor_pos_ = _position; + current_modulation_ = _modulation; + } +} +#endif +#if 0 +void Cibi::updateDisplay(uint32_t _frequency, int _position, int _modulation, bool _tx, int _smeter) +{ + bool update = false; + cibi_channel_t cibi_channel({.channel = -1, + .band = CIBI_BAND_UNKNOWN, + .bis = false}); + uint32_t clarified_freq = add_int32_to_uint32(current_freq_, current_clarifier_); + if (_frequency != clarified_freq) + { + /* TODO: could be optimized to update only the impacted digits */ + view_->setFreq(clarified_freq, BLACK); + view_->setFreq(_frequency, WHITE); + update = true; + /* if withing the 10-11m band, check for Cibi channels */ + /* Outside this range, set HAM bands */ + bool cibiBand = (0 == getCibiChannel(_frequency, &cibi_channel)); + if(cibiBand) + { +#if 0 // def DEBUG + Serial.println(cibi_channel.channel); +#endif // DEBUG + if (current_cibi_channel_.channel != cibi_channel.channel) + { + if(-1 == current_cibi_channel_.channel) + { + view_->setBand(clarified_freq, BLACK); + } + else + { + view_->setCibiChannel(current_cibi_channel_.channel, BLACK); + } + view_->setCibiChannel(cibi_channel.channel, WHITE); + current_cibi_channel_.channel = cibi_channel.channel; + } + if (current_cibi_channel_.band != cibi_channel.band && + CIBI_BAND_UNKNOWN != cibi_channel.band) + { + view_->setCibiBand(current_cibi_channel_.band, BLACK); + view_->setCibiBand(cibi_channel.band, WHITE); + current_cibi_channel_.band = cibi_channel.band; + } + if (current_cibi_channel_.bis != cibi_channel.bis) + { + view_->setCibiChannelBis(current_cibi_channel_.bis, BLACK); + view_->setCibiChannelBis(cibi_channel.bis, WHITE); + current_cibi_channel_.bis = cibi_channel.bis; + } + } + else + { + /* HAM band */ + if(-1 != current_cibi_channel_.channel) + { + view_->setCibiChannel(current_cibi_channel_.channel, BLACK); + view_->setCibiBand(current_cibi_channel_.band, BLACK); + view_->setCibiChannelBis(current_cibi_channel_.bis, BLACK); + } + else + { + view_->setBand(clarified_freq, BLACK); + } + view_->setBand(_frequency, WHITE); + current_cibi_channel_.channel = -1; + current_cibi_channel_.band = CIBI_BAND_UNKNOWN; + } + } + if (_position != current_cursor_pos_) + { + view_->setCursorPos(current_cursor_pos_, BLACK); + view_->setCursorPos(_position, WHITE); + update = true; + } + if (_modulation != current_modulation_) + { + view_->setModulation(current_modulation_, BLACK); + view_->setModulation(_modulation, WHITE); + update = true; + } + if (update) + { + view_->display(); + } +} +#endif + +void Cibi::updateDDS(uint32_t _frequency, int _modulation, bool _tx) +{ + bool enable_fi = _tx; + uint32_t vfo_freq = _frequency; + /* order matters, modulation needs to be updated prior to frequency */ + switch (_modulation) + { + case MOD_AM: + case MOD_FM: + /* store the frequency currently used */ + //dds_.setOutputFreq(DDS::OUTPUT_FI, config_.getFiAm()); + break; + case MOD_CW: + /* store the frequency currently used */ + dds_.setOutputFreq(DDS::OUTPUT_FI, config_.getFiAm()); + enable_fi = true; + break; + case MOD_USB: + /* store the frequency currently used */ + dds_.setOutputFreq(DDS::OUTPUT_FI, config_.getFiUsb()); + enable_fi = true; + break; + case MOD_LSB: + /* store the frequency currently used */ + dds_.setOutputFreq(DDS::OUTPUT_FI, config_.getFiLsb()); + enable_fi = true; + break; + default: + // don't change the clock for unknown modulation type. + break; + } + /* RX/TX frequency is the VF0 + the FI + * So if we want to RX/TX on 27.555MHz in AM + * Set VFO to 27.555MHz - 10.695MHz = 16.860MHz (infradyne) + */ + vfo_freq -= dds_.getOutputFreq(DDS::OUTPUT_FI); +#if 0 + /* RX/TX frequency is the VF0 - the FI + * So if we want to RX/TX on 27.555MHz in AM + * Set VFO to 27.555MHz + 10.695MHz = 38.250MHz (supradyne) + */ + vfo_freq += dds_.getOutputFreq(DDS::OUTPUT_FI); +#endif + dds_.setOutputFreq(DDS::OUTPUT_VFO, vfo_freq); + dds_.enableOutput(DDS::OUTPUT_VFO, true); + /* enable FI oscillator only while transmitting or in CW/SSB mode */ + dds_.enableOutput(DDS::OUTPUT_FI, enable_fi); +} + +void Cibi::updateFreqStep(int cursor_pos) +{ + if (cursor_pos > CURSOR_POS_MAX || + cursor_pos == CURSOR_POS_NONE) + { + freq_step_ = 0; + } + else + { + freq_step_ = quick_pow10(cursor_pos - 1); + } +} + +void Cibi::loop(bool _update_display) +{ + uint32_t new_frequency = current_freq_; + int new_cursor_pos = current_cursor_pos_; + int new_clarifier = current_clarifier_; + int16_t increment = input_.getEncoderIncrement(); + bool click = input_.buttonClicked(); + int new_smeter = input_.getSMeter(); + // smeter adjustment + if(0 != config_.getSMeterAdj()) + { + if(config_.getSMeterAdj() > 0) + { + /* if positive, multiply factor */ + new_smeter = new_smeter * config_.getSMeterAdj(); + } + else if (config_.getSMeterAdj() < 0) + { + /* if negative, division factor */ + new_smeter = new_smeter / abs(config_.getSMeterAdj()); + } + } +#ifdef DEBUG + //Serial.print(F("Sig "));Serial.println(smeter); +#endif // DEBUG + if (_update_display) + { + /* initial display */ + init(); + /* waiting for next call to loop() */ + return; + } + if(input_.getIncrementMode() == Input::INCREMENT_MANUAL) + { + /* Increment mode: manual + ====================== + Click the encoder to select a digit + Change the selected digit value by encoder rotation + */ + if (click) + { + new_cursor_pos++; + if (new_cursor_pos > CURSOR_POS_MAX) + { + new_cursor_pos = CURSOR_POS_NONE; + } + } + updateFreqStep(new_cursor_pos); + } + else + { + /* Increment mode: step + ==================== + Encoder rotation change frequency value by step + Click to scan + */ + new_cursor_pos = CURSOR_POS_NONE; + freq_step_ = config_.getFreqStepIncrement(); + if(click && !scanning_) { + scanning_ = true; + increment = 1; + } + else if ((click || input_.getTx()) && scanning_) + { + scanning_ = false; + increment = 0; + } + else if (increment && scanning_) + { + scanning_ = false; + } + + if(scanning_) { + /* TODO: does scan threshold needs be in the config? + * Playing with RF gain does allow to play around this level + */ + if(new_smeter > 10) { + increment = -1; // seems that measure takes one loop, so return to previous freq + scanning_ = false; + } else { + increment = 1; + } + } + } + + if (increment) + { + if (increment < 0 && (freq_step_ * abs(increment)) > new_frequency) + { + /* avoid negative values */ + new_frequency = 0; + } + else + { + new_frequency += ((uint32_t)increment * freq_step_); + } + + /* Limits with rotation */ + if (new_frequency > config_.getCibiMaxFreq()) + new_frequency = config_.getCibiMinFreq(); + else if (new_frequency < config_.getCibiMinFreq()) + new_frequency = config_.getCibiMaxFreq(); + } + + /* save new frequency to EEPROM after 30 seconds of usage */ + if (new_frequency != current_freq_) + { + freq_change_time_ = millis(); + input_.setBuiltInLED(HIGH); + } + else if(freq_change_time_ != 0) + { + if(millis() - freq_change_time_ > 30000) + { + /* about 30 seconds on the same frequency, save it */ + config_.setCibiFreq(current_freq_); + freq_change_time_ = 0; + input_.setBuiltInLED(LOW); + } + } + + if (new_frequency != current_freq_ || + (input_.getClarifier() + config_.getClarifierAdj()) != current_clarifier_ || + input_.getModulation() != current_modulation_ || + new_cursor_pos != current_cursor_pos_ || + input_.getTx() != current_tx_ +#ifdef SMETER_DISPLAY + || new_smeter != current_smeter_ +#endif + ) + { + /* Update display and context with newer values */ + if(!input_.getTx()) + { + /* Apply clarifier here if we are receiving */ + new_clarifier = input_.getClarifier() + config_.getClarifierAdj(); + } + else + { + new_clarifier = 0; + } + + updateDisplay(add_int32_to_uint32(new_frequency, new_clarifier), new_cursor_pos, input_.getModulation(), input_.getTx(), new_smeter); + updateDDS(add_int32_to_uint32(new_frequency, new_clarifier), input_.getModulation(), input_.getTx()); + current_freq_ = new_frequency; + current_clarifier_ = new_clarifier; + current_modulation_ = input_.getModulation(); + current_cursor_pos_ = new_cursor_pos; + current_tx_ = input_.getTx(); + current_smeter_ = new_smeter; + } +} + +const uint32_t& Cibi::getCurrentFreq() +{ + return current_freq_; +} + +#if 0 +int Cibi::getBand(uint32_t _frequency) +{ + /* + * Display band names according to + * https://en.wikipedia.org/wiki/Amateur_radio_frequency_allocations + */ + const static uint32_t BAND_2200_LOW PROGMEM = 135700; + const static uint32_t BAND_2200_HI PROGMEM = 135800; + const static uint32_t BAND_1750_LOW PROGMEM = 160000; + const static uint32_t BAND_1750_HI PROGMEM = 190000; + const static uint32_t BAND_630_LOW PROGMEM = 472000; + const static uint32_t BAND_630_HI PROGMEM = 479000; + const static uint32_t BAND_160_LOW PROGMEM = 1800000; + const static uint32_t BAND_160_HI PROGMEM = 2000000; + const static uint32_t BAND_80_LOW PROGMEM = 3500000; + const static uint32_t BAND_80_HI PROGMEM = 4000000; + const static uint32_t BAND_60_LOW PROGMEM = 5332000; + const static uint32_t BAND_60_HI PROGMEM = 5450000; + const static uint32_t BAND_40_LOW PROGMEM = 7000000; + const static uint32_t BAND_40_HI PROGMEM = 7300000; + const static uint32_t BAND_30_LOW PROGMEM = 10100000; + const static uint32_t BAND_30_HI PROGMEM = 10150000; + const static uint32_t BAND_20_LOW PROGMEM = 14000000; + const static uint32_t BAND_20_HI PROGMEM = 14350000; + const static uint32_t BAND_17_LOW PROGMEM = 18068000; + const static uint32_t BAND_17_HI PROGMEM = 18168000; + const static uint32_t BAND_15_LOW PROGMEM = 21000000; + const static uint32_t BAND_15_HI PROGMEM = 21450000; + const static uint32_t BAND_12_LOW PROGMEM = 24890000; + const static uint32_t BAND_12_HI PROGMEM = 24990000; + const static uint32_t BAND_11_LOW PROGMEM = 25000000; + const static uint32_t BAND_11_HI PROGMEM = 28000000; + const static uint32_t BAND_10_LOW PROGMEM = 28000000; + const static uint32_t BAND_10_HI PROGMEM = 29700000; + const static uint32_t BAND_6_LOW PROGMEM = 50000000; + const static uint32_t BAND_6_HI PROGMEM = 54000000; + const static uint32_t BAND_2_LOW PROGMEM = 144000000; + const static uint32_t BAND_2_HI PROGMEM = 148000000; + if (_frequency > BAND_10_LOW && _frequency <= BAND_10_HI) return BAND_10; + else if (_frequency > BAND_11_LOW && _frequency <= BAND_11_HI) return BAND_11; + else if (_frequency > BAND_12_LOW && _frequency <= BAND_12_HI) return BAND_12; + else if (_frequency > BAND_15_LOW && _frequency <= BAND_15_HI) return BAND_15; + else if (_frequency > BAND_17_LOW && _frequency <= BAND_17_HI) return BAND_17; + else if (_frequency > BAND_20_LOW && _frequency <= BAND_20_HI) return BAND_20; + else if (_frequency > BAND_30_LOW && _frequency <= BAND_30_HI) return BAND_30; + else if (_frequency > BAND_2200_LOW && _frequency <= BAND_2200_HI) return BAND_2200; + else if (_frequency > BAND_1750_LOW && _frequency <= BAND_1750_HI) return BAND_1750; + else if (_frequency > BAND_630_LOW && _frequency <= BAND_630_HI) return BAND_630; + else if (_frequency > BAND_160_LOW && _frequency <= BAND_160_HI) return BAND_160; + else if (_frequency > BAND_80_LOW && _frequency <= BAND_80_HI) return BAND_80; + else if (_frequency > BAND_60_LOW && _frequency <= BAND_60_HI) return BAND_60; + else if (_frequency > BAND_40_LOW && _frequency <= BAND_40_HI) return BAND_40; + else if (_frequency > BAND_6_LOW && _frequency <= BAND_6_HI) return BAND_6; + else if (_frequency > BAND_2_LOW && _frequency <= BAND_2_HI) return BAND_2; + else return BAND_UNKNOWN; +} +#endif /* 0 */ diff --git a/src/cibidisplay.cpp b/src/cibidisplay.cpp new file mode 100644 index 0000000..b0b7972 --- /dev/null +++ b/src/cibidisplay.cpp @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2019, Vincent Hervieux vincent.hervieux@gmail.com + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the author Vincent Hervieux, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/*** + *** Display: using an OLED SSD1306 + ***/ +#include "cibidisplay.h" +#include "vfo.h" + +#include +#include + + +CibiDisplay::CibiDisplay(U8G2 &_display): +display_(_display) +{ + +} + +CibiDisplay::CibiDisplay(const CibiDisplay &_cibidisplay): +display_(_cibidisplay.display_) +{ + +} + +CibiDisplay::~CibiDisplay() +{ + display_.clearDisplay(); +} + +void CibiDisplay::init() +{ + const static int16_t SMETER_TOP_X PROGMEM = 5; + const static int16_t SMETER_TOP_Y PROGMEM = 8; + const static int16_t FREQ_DOT_TEXT_POS_X PROGMEM = 32; + const static int16_t MHZ_TEXT_POS_X PROGMEM = 120; + const static int16_t H_LINE_WIDTH PROGMEM = 118; + const static int16_t H_LINE_POS_X PROGMEM = 0; +#ifdef SMETER_DISPLAY + /* S meter frame */ + display_.setFont(u8g2_font_6x10_tr); + display_.setCursor(SMETER_TOP_X,SMETER_TOP_Y); + display_.setDrawColor(WHITE); + display_.print(F("SIG.1.3.5.7.9..+30dB")); +#else +/* S meter frame */ + display_.setFont(u8g2_font_6x10_tr); + display_.setCursor(SMETER_TOP_X,SMETER_TOP_Y+4); + display_.setDrawColor(WHITE); + display_.print(SMETER_REPLACEMENT_TEXT); +#endif + /* Frequency dot */ + display_.drawDisc(FREQ_DOT_TEXT_POS_X, 35, 2, WHITE); + /* Mhz sign */ + display_.setCursor(MHZ_TEXT_POS_X,24); + display_.print(F("M")); + display_.setCursor(MHZ_TEXT_POS_X,33); + display_.print(F("H")); + display_.setCursor(MHZ_TEXT_POS_X,40); + display_.print(F("z")); + /* 2 lines separation between informations */ + display_.drawHLine(H_LINE_POS_X, 16, H_LINE_WIDTH); + display_.drawHLine(H_LINE_POS_X, 40, H_LINE_WIDTH); +} + +void CibiDisplay::clear() +{ + display_.clearBuffer(); +} + +void CibiDisplay::display() +{ + init(); + display_.sendBuffer(); +} + +void CibiDisplay::setFreq(uint32_t _freq, uint16_t _color) +{ + const static uint16_t MAX_FREQ_STR_LENGTH PROGMEM = 8; /* 30000000*/ + char str[MAX_FREQ_STR_LENGTH + 1] = {0}; + char big_str[4] = {0}; + snprintf(str, MAX_FREQ_STR_LENGTH + 1, "%08ld", _freq); + strncpy (big_str, str, 2 * sizeof(char)); + display_.setFont(u8g2_font_crox5hb_tr); + display_.setCursor(FREQ_DECIMAL_TEXT_POS_X,FREQ_TEXT_POS_Y); + display_.setDrawColor(_color); + display_.print(big_str); + display_.setCursor(FREQ_FRACTIONNAL_TEXT_POS_X,FREQ_TEXT_POS_Y); + display_.print(str + 2 * sizeof(char)); +} + +void CibiDisplay::setCursorPos(int _position, uint16_t _color) +{ + const static int16_t CURSOR_TEXT_POS_Y PROGMEM = 37; + const static int16_t CURSOR_DECIMAL_TEXT_POS_X PROGMEM = 17; + const static int16_t CURSOR_FRACTIONAL_TEXT_POS_X PROGMEM = FREQ_DECIMAL_TEXT_POS_X + 35; + const static int16_t CURSOR_WIDTH PROGMEM = 10; + const static int16_t CURSOR_HEIGHT PROGMEM = 2; + const static int16_t CURSOR_SHIFT PROGMEM = 14; + int16_t x; + if(CURSOR_POS_NONE == _position) return; + if(CURSOR_POS_FREQ_DIG7 == _position) + { + x = CURSOR_DECIMAL_TEXT_POS_X; + } + else + { + x = CURSOR_FRACTIONAL_TEXT_POS_X + + (CURSOR_POS_FREQ_DIG6 - _position) * CURSOR_SHIFT; + } + display_.setDrawColor(_color); + display_.drawBox(x, CURSOR_TEXT_POS_Y, CURSOR_WIDTH, CURSOR_HEIGHT); +} + +void CibiDisplay::setModulation(int _modulation, uint16_t _color) +{ + const static int16_t MOD_TEXT_POS_X PROGMEM = 70; + const static int16_t MOD_TEXT_POS_Y PROGMEM = 64; + display_.setFont(u8g2_font_crox5hb_tr); + display_.setCursor(MOD_TEXT_POS_X,MOD_TEXT_POS_Y); + display_.setDrawColor(_color); + switch (_modulation) + { + case MOD_CW: + display_.print(F(" CW")); + break; + case MOD_AM: + display_.print(F(" AM")); + break; + case MOD_FM: + display_.print(F(" FM")); + break; + case MOD_USB: + display_.print(F("USB")); + break; + case MOD_LSB: + default: + display_.print(F("LSB")); + break; + } +} + +void CibiDisplay::setCibiChannel(int _channel, uint16_t _color) +{ + const static int16_t CIBI_CHANNEL_TEXT_POS_X PROGMEM = 16; + const static int16_t CIBI_CHANNEL_TEXT_POS_Y PROGMEM = 64; + char str[3] = {0}; + if(-1 != _channel) + { + display_.setFont(u8g2_font_crox5hb_tr); + display_.setDrawColor(_color); + if(_channel < 10) + display_.setCursor(CIBI_CHANNEL_TEXT_POS_X + 14, CIBI_CHANNEL_TEXT_POS_Y); + else + display_.setCursor(CIBI_CHANNEL_TEXT_POS_X, CIBI_CHANNEL_TEXT_POS_Y); + snprintf(str, sizeof(str), "%d", _channel); + display_.print(str); + } +} + +void CibiDisplay::setCibiChannelBis(bool _bis, uint16_t _color) +{ + const static int16_t CIBI_CHANNEL_BIS_TEXT_POS_X PROGMEM = 45; + const static int16_t CIBI_CHANNEL_BIS_POS_Y PROGMEM = 50; + if(_bis) + { + display_.setFont(u8g2_font_6x10_tr); + display_.setDrawColor(_color); + display_.setCursor(CIBI_CHANNEL_BIS_TEXT_POS_X, CIBI_CHANNEL_BIS_POS_Y); + display_.print(F("bis")); + } +} + +void CibiDisplay::setCibiBand(int _band, uint16_t _color) +{ + const static int CIBI_BAND_NORMAL PROGMEM = 3; + if(CIBI_BAND_NORMAL == _band) return; + int deviation = CIBI_BAND_NORMAL - _band; + display_.setDrawColor(_color); + for(int i = 0; i < abs(deviation); i++) + { + display_.drawHLine(7, 58 - i*6, 5); + } + if(deviation < 0) + { + /* + signs */ + for(int i = 0; i < abs(deviation); i++) + { + display_.drawVLine(9, 56 - i*6, 5); + } + } +} + +void CibiDisplay::setBand(uint32_t _freq, uint16_t _color) +{ + const static int16_t BAND_TEXT_POS_X PROGMEM = FREQ_DECIMAL_TEXT_POS_X; + const static int16_t BAND_TEXT_POS_Y PROGMEM = 64; + display_.setFont(u8g2_font_crox5hb_tr); + display_.setCursor(BAND_TEXT_POS_X,BAND_TEXT_POS_Y); + display_.setDrawColor(_color); + if(135700 <=_freq && _freq <= 137800) + { + /* For this band do not print 'm' to earn on string's lenght */ + display_.print(F("2222")); + } + else if(472000 <=_freq && _freq <= 479000) + { + display_.print(F("630m")); + } + else if(1800000 <=_freq && _freq <= 2000000) + { + display_.print(F("160m")); + } + else if(3500000 <= _freq && _freq <= 4000000) + { + display_.print(F("80m")); + } + else if(5351500 <= _freq && _freq <= 5366500) + { + display_.print(F("60m")); + } + else if(7000000 <= _freq && _freq <= 7300000) + { + display_.print(F("40m")); + } + else if(10100000 <= _freq && _freq <= 10150000) + { + display_.print(F("30m")); + } + else if(14000000 <= _freq && _freq <= 14350000) + { + display_.print(F("20m")); + } + else if(18068000 <= _freq && _freq <= 18168000) + { + display_.print(F("17m")); + } + else if(21000000 <= _freq && _freq <= 21450000) + { + display_.print(F("15m")); + } + else if(24890000 <= _freq && _freq <= 24990000) + { + display_.print(F("12m")); + } + else if(28000000 <= _freq && _freq <= 29700000) + { + display_.print(F("10m")); + } + else + { + display_.print(F("OOB")); + } +} + +#ifdef SMETER_DISPLAY +void CibiDisplay::setSMeter(int _signal) +{ + // cursor + display_.setDrawColor(BLACK); + display_.drawBox(12, 11, 116, 4); + display_.setDrawColor(WHITE); + for(int index = 0; index < _signal && index < 21; index++) + { + display_.drawBox(12 + index * 5, 11, 4, 4); + } + display(); +} +#endif + +void CibiDisplay::setTx(bool _tx) +{ + // invert black and white of the displayed frequency in TX + const static int16_t H_LINE_WIDTH PROGMEM = 118; + const static int16_t H_LINE_POS_X PROGMEM = 0; + const static int16_t FREQ_DOT_TEXT_POS_X PROGMEM = 32; + display_.setDrawColor((_tx? WHITE : BLACK)); + display_.drawBox(H_LINE_POS_X, 17, H_LINE_WIDTH, 23); +} diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..7b409e0 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2019, Vincent Hervieux vincent.hervieux@gmail.com + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the author Vincent Hervieux, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "utils.h" +#include "vfo.h" + +#include + +const static uint32_t EEPROM_HEADER PROGMEM = 0xDEADBEEF; +const static uint32_t DEFAULT_CIBI_FREQ PROGMEM = 27555000; +const static uint32_t DEFAULT_CIBI_MIN_FREQ PROGMEM = 25000000; +const static uint32_t DEFAULT_CIBI_MAX_FREQ PROGMEM = 30000000; +const static uint32_t DEFAULT_FREQ_STEP_INC PROGMEM = 5000; +/* On superstar 3900 and derivated: + - reference oscillator is at 10240KHz + - FI is either 10695KHz(CW,AM,FM), 10692.5KHz(USB), 10697.5KHz(LSB) */ +const static uint32_t DEFAULT_FI_FREQ_AM PROGMEM = 10695000; +const static uint32_t DEFAULT_FI_FREQ_USB PROGMEM = 10692500; +const static uint32_t DEFAULT_FI_FREQ_LSB PROGMEM = 10697500; +const static int32_t DEFAULT_VFO_ADJ PROGMEM = 0; +const static int32_t DEFAULT_CLARIFIER_ADJ PROGMEM = 0; +const static int32_t DEFAULT_SMETER_ADJ PROGMEM = 0; +Config::Config(Input &_input, U8G2 &_display, DDS &_dds): +context_({ + .eeprom_header = EEPROM_HEADER, + .cibi_frequency = DEFAULT_CIBI_FREQ, + .cibi_min_freq = DEFAULT_CIBI_MIN_FREQ, + .cibi_max_freq = DEFAULT_CIBI_MAX_FREQ, + .freq_step_inc = DEFAULT_FREQ_STEP_INC, + .fi_am = DEFAULT_FI_FREQ_AM, + .fi_usb = DEFAULT_FI_FREQ_USB, + .fi_lsb = DEFAULT_FI_FREQ_LSB, + .vfo_adj = DEFAULT_VFO_ADJ, + .clarifier_adj = DEFAULT_CLARIFIER_ADJ, + .smeter_adj = DEFAULT_SMETER_ADJ +}), +input_(_input), +dds_(_dds), +current_cursor_pos_(CURSOR_POS_NONE), +current_menu_index_(CONFIG_CIBI_FREQ), +current_value_(&context_.cibi_frequency) +{ + view_ = new ConfigDisplay(_display); +} + +Config::Config(const Config& _config) : +context_({ + .eeprom_header = _config.context_.eeprom_header, + .cibi_frequency = _config.context_.cibi_frequency, + .cibi_min_freq = _config.context_.cibi_min_freq, + .cibi_max_freq = _config.context_.cibi_max_freq, + .freq_step_inc = _config.context_.freq_step_inc, + .fi_am = _config.context_.fi_am, + .fi_usb = _config.context_.fi_usb, + .fi_lsb = _config.context_.fi_lsb, + .vfo_adj = _config.context_.vfo_adj, + .clarifier_adj = _config.context_.clarifier_adj, + .smeter_adj = _config.context_.smeter_adj, +}), +input_(_config.input_), +dds_(_config.dds_), +view_(_config.view_), +current_cursor_pos_(_config.current_cursor_pos_), +current_menu_index_(_config.current_menu_index_), +current_value_(_config.current_value_) +{ + +} + +Config::~Config() +{ + delete view_; +} + +void Config::loop(bool _update_display) +{ + bool update = _update_display; + uint32_t freq_step = 0; + int increment = 0; + if(input_.buttonClicked()) + { + update = true; + current_cursor_pos_++; + if(current_cursor_pos_ >= CURSOR_POS_MAX) + { + current_cursor_pos_ = CURSOR_POS_NONE; + } + } + freq_step = quick_pow10(current_cursor_pos_-1); + increment = (int)input_.getEncoderIncrement(); + if(increment) + { + update = true; + if(CURSOR_POS_NONE == current_cursor_pos_) + { + /* change between menu items */ + current_menu_index_ += increment; + + if (current_menu_index_ >= CONFIG_MAX) + { + current_menu_index_ = 0; + } + else if (current_menu_index_ < 0) + { + current_menu_index_ = CONFIG_MAX - 1; + } + + /* change displayed item value */ + switch (current_menu_index_) + { + case CONFIG_CIBI_FREQ: + current_value_ = &context_.cibi_frequency; + break; + case CONFIG_CIBI_MIN_FREQ: + current_value_ = &context_.cibi_min_freq; + break; + case CONFIG_CIBI_MAX_FREQ: + current_value_ = &context_.cibi_max_freq; + break; + case CONFIG_FREQ_STEP: + current_value_ = &context_.freq_step_inc; + break; + case CONFIG_FI_AM: + current_value_ = &context_.fi_am; /* FI AM/FM/CW */ + break; + case CONFIG_FI_USB: + current_value_ = &context_.fi_usb; /* FI USB */ + break; + case CONFIG_FI_LSB: + current_value_ = &context_.fi_lsb; /* FI LSB */ + break; + case CONFIG_VFO_ADJ: + current_value_ = (uint32_t*)&context_.vfo_adj; + break; + case CONFIG_CLARIFIER_ADJ: + current_value_ = (uint32_t*)&context_.clarifier_adj; + break; + case CONFIG_SMETER_ADJ: + current_value_ = (uint32_t*)&context_.smeter_adj; + break; + default: + return; + } + } + else + { + if((current_menu_index_ != CONFIG_VFO_ADJ) && + (current_menu_index_ != CONFIG_CLARIFIER_ADJ) && + (current_menu_index_ != CONFIG_SMETER_ADJ) && + (increment < 0) && + (freq_step * abs(increment)) > (*current_value_)) + { + /* avoid negative values */ + (*current_value_) = 0; + } + else + { + (*current_value_) += ((uint32_t)increment * freq_step); + } + } + } + + if(update) + { + view_->clear(); + + if(CURSOR_POS_NONE == current_cursor_pos_) + { + view_->highlightTitle(WHITE); + view_->setTitle(current_menu_index_, BLACK); + } + else + { + view_->setTitle(current_menu_index_, WHITE); + } + + if (current_menu_index_ != CONFIG_VFO_ADJ && + current_menu_index_ != CONFIG_CLARIFIER_ADJ && + current_menu_index_ != CONFIG_SMETER_ADJ) + { + view_->setValueUint((*current_value_), WHITE); + } + else + { + view_->setValueInt((*(int32_t*)current_value_), WHITE); + } + + if(current_menu_index_ == CONFIG_VFO_ADJ) + { + /* set si5351 correction here */ + dds_.setOutputCorrection((*(int32_t*)current_value_)); + } + + view_->setCursorPos(current_cursor_pos_, WHITE); + view_->display(); + } +} + +int Config::open() +{ + eeprom_read_block((void*)&context_, (void*)0, sizeof(context_t)); + if(context_.eeprom_header != EEPROM_HEADER) + { + /* EEPROM first time use */ + context_.eeprom_header = EEPROM_HEADER; + context_.cibi_frequency = DEFAULT_CIBI_FREQ; + context_.cibi_min_freq = DEFAULT_CIBI_MIN_FREQ; + context_.cibi_max_freq = DEFAULT_CIBI_MAX_FREQ; + context_.freq_step_inc = DEFAULT_FREQ_STEP_INC; + context_.fi_am = DEFAULT_FI_FREQ_AM; + context_.fi_usb = DEFAULT_FI_FREQ_USB; + context_.fi_lsb = DEFAULT_FI_FREQ_LSB; + context_.vfo_adj = DEFAULT_VFO_ADJ; + context_.smeter_adj = DEFAULT_SMETER_ADJ; + } + return 0; +} + +int Config::save() +{ + eeprom_update_block((const void*)&context_, (void*)0, sizeof(context_t)); + return 0; +} + +const uint32_t& Config::getCibiFreq() +{ + return context_.cibi_frequency; +} + +const uint32_t& Config::getCibiMinFreq() +{ + return context_.cibi_min_freq; +} + +const uint32_t& Config::getCibiMaxFreq() +{ + return context_.cibi_max_freq; +} + +const uint32_t& Config::getFreqStepIncrement() +{ + return context_.freq_step_inc; +} + +const uint32_t& Config::getFiAm() +{ + return context_.fi_am; +} + +const uint32_t& Config::getFiUsb() +{ + return context_.fi_usb; +} + +const uint32_t& Config::getFiLsb() +{ + return context_.fi_lsb; +} + +const int32_t& Config::getVFOAdj() +{ + return context_.vfo_adj; +} + +const int32_t& Config::getClarifierAdj() +{ + return context_.clarifier_adj; +} + +const int32_t& Config::getSMeterAdj() +{ + return context_.smeter_adj; +} + +void Config::setCibiFreq(const uint32_t& _freq) +{ + if(_freq != context_.cibi_frequency) + { + context_.cibi_frequency = _freq; + save(); + } +} diff --git a/src/configdisplay.cpp b/src/configdisplay.cpp new file mode 100644 index 0000000..8c2cafe --- /dev/null +++ b/src/configdisplay.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2019, Vincent Hervieux vincent.hervieux@gmail.com + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the author Vincent Hervieux, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "configdisplay.h" +#include "vfo.h" +#include +#include + +ConfigDisplay::ConfigDisplay(U8G2 &_display): +display_(_display) +{ + +} + +ConfigDisplay::~ConfigDisplay() +{ + display_.clearDisplay(); +} + +void ConfigDisplay::init() +{ + display_.drawHLine(0, 15, SCREEN_WIDTH); + display_.drawHLine(0, 31, SCREEN_WIDTH); +} + +void ConfigDisplay::clear() +{ + display_.clearBuffer(); +} + +void ConfigDisplay::display() +{ + display_.updateDisplay(); +} + +void ConfigDisplay::clearDisplay() +{ + display_.clearDisplay(); +} + +void ConfigDisplay::setTitle(int _title_index, uint16_t _color) +{ + const static int16_t TITLE_POS_X PROGMEM = 4; + const static int16_t TITLE_POS_Y PROGMEM = 27; + const static int16_t INDEX_POS_X PROGMEM = 35; + const static int16_t INDEX_POS_Y PROGMEM = 11; + const static int16_t MAX_STR_LENGTH PROGMEM = 20; + const static char title_cibi_default_freq[] PROGMEM = "CIBI DEFAULT FREQ."; + const static char title_cibi_min_freq[] PROGMEM = " CIBI FREQ. MIN."; + const static char title_cibi_max_freq[] PROGMEM = " CIBI FREQ. MAX."; + const static char title_freq_step_inc[] PROGMEM = "FREQ. STEP INCREM."; + const static char title_fi_am_fm[] PROGMEM = " FI AM/FM"; + const static char title_fi_usb[] PROGMEM = " FI USB"; + const static char title_fi_lsb[] PROGMEM = " FI LSB"; + const static char title_vfo_adj[] PROGMEM = " VFO FREQ. ADJ"; + const static char title_clarifier_adj[] PROGMEM = "CLARIFIER FREQ. ADJ"; + const static char title_smeter_adj[] PROGMEM = " S-METER ADJ"; + const static char* const titles[] PROGMEM = { + title_cibi_default_freq, + title_cibi_min_freq, + title_cibi_max_freq, + title_freq_step_inc, + title_fi_am_fm, + title_fi_usb, + title_fi_lsb, + title_vfo_adj, + title_clarifier_adj, + title_smeter_adj + }; + char str[MAX_STR_LENGTH] = {0}; + int menu_index = _title_index + 1; + int menu_items = sizeof(titles) / sizeof(char*); + //display_.setFont(u8g2_font_6x10_tr); + display_.setFont(u8g2_font_6x12_tr); + display_.setDrawColor(_color); + snprintf(str, MAX_STR_LENGTH + 1, "CONFIG %d/%d :", menu_index, menu_items); + display_.setCursor(INDEX_POS_X, INDEX_POS_Y); + display_.print(str); + display_.setCursor(TITLE_POS_X,TITLE_POS_Y); + strcpy_P(str, (char*)pgm_read_word(&(titles[_title_index]))); + display_.print(str); + display_.drawHLine(0, 15, SCREEN_WIDTH); + display_.drawHLine(0, 31, SCREEN_WIDTH); +} + +void ConfigDisplay::highlightTitle(uint16_t _color) +{ + display_.setDrawColor(_color); + display_.drawBox(0, 0, SCREEN_WIDTH, 31); +} + +void ConfigDisplay::setValueUint(uint32_t _value, uint16_t _color) +{ + char str[MAX_STR_LENGTH] = {0}; + snprintf(str, MAX_STR_LENGTH + 1, "%08lu", _value); + display_.setFont(u8g2_font_7x14B_tf); + display_.setCursor(VALUE_POS_X,VALUE_POS_Y); + display_.setDrawColor(_color); + display_.print(str); +} + +void ConfigDisplay::setValueInt(int32_t _value, uint16_t _color) +{ + const static uint16_t SIGN_SHIFT PROGMEM = 9; + char str[MAX_STR_LENGTH] = {0}; + display_.setDrawColor(_color); + if(_value < 0) { + display_.drawHLine(VALUE_POS_X - SIGN_SHIFT, VALUE_POS_Y - 8, 5); + } + snprintf(str, MAX_STR_LENGTH + 1, "%08ld", abs(_value)); + display_.setFont(u8g2_font_7x14B_tr); + display_.setCursor(VALUE_POS_X,VALUE_POS_Y); + display_.print(str); +} + +void ConfigDisplay::setCursorPos(int _position, uint16_t _color) +{ + const static int16_t CURSOR_POS_X PROGMEM = 25; + const static int16_t CURSOR_POS_Y PROGMEM = 58; + const static int16_t CURSOR_BIG_WIDTH PROGMEM = 8; + const static int16_t CURSOR_SHIFT PROGMEM = 9; + int16_t x; + display_.setDrawColor(_color); + if(_position >= CURSOR_POS_FREQ_DIG1 && _position <= CURSOR_POS_FREQ_DIG7) + { + x = CURSOR_POS_X + (8 - _position) * CURSOR_SHIFT; + display_.drawBox(x, CURSOR_POS_Y, CURSOR_BIG_WIDTH, 2); + } +} diff --git a/src/dds.cpp b/src/dds.cpp new file mode 100644 index 0000000..8d72de8 --- /dev/null +++ b/src/dds.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019, Vincent Hervieux vincent.hervieux@gmail.com + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the author Vincent Hervieux, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/*** + *** DDS: Direct Digital Synthesis + *** The three si5351 clock outputs are square wave + ***/ +#include "dds.h" +#include "vfo.h" + +DDS::DDS(): +initialized_(false) +{ + /* initialize stored values */ + for(int i = 0; i < OUTPUT_MAX; i++) + current_freq_[i] = 0; +} + +DDS::~DDS() +{ + clockgen_.off(); +} + +void DDS::init() +{ + if(!initialized_) + { + /* This lib has no way to know if I2C communication is effective */ + clockgen_.init(XTALFREQ); + /* Set drive current to its maximum */ + clockgen_.setPower(OUTPUT_FI, SIOUT_8mA); + enabled_[OUTPUT_FI] = true; + /* 2 mA : FM is weak */ + /* 4 mA : FM is distorded */ + /* 6 mA : FM is distorded */ + /* 8 mA : FM is really distorded - adjust VR5*/ + clockgen_.setPower(OUTPUT_VFO, SIOUT_8mA); + enabled_[OUTPUT_VFO] = true; + enabled_[OUTPUT_UNUSED] = false; + initialized_ = true; + } +} + +const uint32_t& DDS::getOutputFreq(output_t _output) +{ + return current_freq_[_output]; +} + +void DDS::setOutputFreq(output_t _output, uint32_t _frequency) +{ + clockgen_.setFreq(_output, _frequency); + /* store the frequency currently used */ + current_freq_[_output] = _frequency; +} + +void DDS::setOutputCorrection(int32_t _correction) +{ + /* set quartz correction */ + clockgen_.correction(_correction); + clockgen_.setFreq(OUTPUT_VFO, current_freq_[OUTPUT_VFO]); + clockgen_.setFreq(OUTPUT_FI, current_freq_[OUTPUT_FI]); +} + +void DDS::enableOutput(output_t _output, bool _enable) +{ + if(_enable && !enabled_[_output]) + { + clockgen_.enable(_output); + enabled_[_output] = true; + } + else if(!_enable && enabled_[_output]) + { + clockgen_.disable(_output); + enabled_[_output] = false; + } +}