/*  -*-  mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8  -*-  */
/************************************************************************************
 **                                                                                 **
 **                                        UHSDR                                    **
 **               a powerful firmware for STM32 based SDR transceivers              **
 **                                                                                 **
 **---------------------------------------------------------------------------------**
 **                                                                                 **
 **  File name:                                                                     **
 **  Description:                                                                   **
 **  Last Modified:                                                                 **
 **  Licence:		GNU GPLv3                                                      **
 ************************************************************************************/

// Common
#include "uhsdr_board.h"
#include "profiling.h"
#include <assert.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <src/uhsdr_version.h>
#include "hardware/uhsdr_board_config.h"
#include "ui_lcd_layouts.h"
#include "ui_menu.h"
#include "uhsdr_rtc.h"
#include "adc.h"
#include "drivers/ui/oscillator/osc_si5351a.h"
#include "audio_nr.h"
#include "uhsdr_keypad.h"
#include "serial_eeprom.h"
#include "ui.h"
// LCD
#include "ui_lcd_hy28.h"
#include "ui_spectrum.h"

#include "freedv_uhsdr.h"

// Encoders
#include "ui_rotary.h"

#include "cat_driver.h"

// Codec control
#include "codec.h"

#include "audio_management.h"
#include "ui_driver.h"

#include "ui_configuration.h"
#include "config_storage.h"

#include "cw_gen.h"
#include "uhsdr_digi_buffer.h"

#include "radio_management.h"
#include "soft_tcxo.h"

#include "rtty.h"
#include "cw_decoder.h"
#include "psk.h"

#include "audio_convolution.h"
#include "audio_agc.h"
#include "ui_menu_internal.h"

#define SPLIT_ACTIVE_COLOUR         		Yellow      // colour of "SPLIT" indicator when active
#define SPLIT_INACTIVE_COLOUR           	Grey        // colour of "SPLIT" indicator when NOT active
#define COL_PWR_IND                 		White



//static void     UiDriver_CreateMainFreqDisplay();

static void     UiDriver_CreateMeters(void);
static void     UiDriver_DeleteMeters(void);
static void 	UiDriver_DrawSMeter(ushort color);
//
static void 	UiDriver_UpdateTopMeterA(uchar val);
static void 	UiDriver_UpdateBtmMeter(float val, uchar warn);

static void 	UiDriver_InitFrequency(void);
//

static void     UiDriver_UpdateLcdFreq(ulong dial_freq,ushort color,ushort mode);
static bool 	UiDriver_IsButtonPressed(ulong button_num);
static void		UiDriver_TimeScheduler(void);				// Also handles audio gain and switching of audio on return from TX back to RX
static void 	UiDriver_ChangeToNextDemodMode(bool select_alternative_mode);
static void 	UiDriver_ChangeBand(bool is_up);
static bool 	UiDriver_CheckFrequencyEncoder(void);

static void     UiDriver_DisplayBand(const BandInfo* band);
//static void     UiDriver_DisplayBandForFreq(uint32_t freq);
static void     UiDriver_DisplayBandForFreq(uint32_t freq, bool force);

static void     UiDriver_DisplayEncoderOneMode(void);
static void     UiDriver_DisplayEncoderTwoMode(void);
static void     UiDriver_DisplayEncoderThreeMode(void);

static void 	UiDriver_DisplayNoiseBlanker(bool encoder_active);
static void 	UiDriver_DisplayDSPMode(bool encoder_active);
static void 	UiDriver_DisplayTone(bool encoder_active);
static void 	UiDriver_DisplayRit(bool encoder_active);
static void     UiDriver_DisplayAfGain(bool encoder_active);
static void     UiDriver_DisplayRfGain(bool encoder_active);
static void     UiDriver_DisplaySidetoneGain(bool encoder_active);
static void     UiDriver_DisplayCmpLevel(bool encoder_active);
static void     UiDriver_DisplayKeyerSpeed(bool encoder_active);
static void     UiDriver_DisplayLineInModeAndGain(bool encoder_active);
static void     UiDriver_DisplayPWRGain(bool encoder_active);

//static void     UiDriver_DisplayMemoryLabel();


static void 	UiDriver_DisplayModulationType(void);
static void 	UiDriver_DisplayPowerLevel(void);
static void     UiDriver_DisplayTemperature(int temp);
static void     UiDriver_DisplayVoltage(void);

static void 	UiDriver_HandleSMeter(void);
static void 	UiDriver_HandleTXMeters(void);
static bool     UiDriver_HandleVoltage(void);

#if 0
static void 	UiDriverUpdateLoMeter(uchar val,uchar active);
#endif
static void     UiDriver_CreateVoltageDisplay(void);

static void 	UiDriver_HandleLoTemperature(void);


static bool	    UiDriver_LoadSavedConfigurationAtStartup(void);
static bool	    UiDriver_TouchscreenCalibration(void);

static void     UiDriver_PowerDownCleanup(bool saveConfiguration);

static void UiDriver_HandlePowerLevelChange(const BandInfo* band, uint8_t power_level);
static void UiDriver_HandleBandButtons(uint16_t button);

static void UiDriver_KeyTestScreen(void);

static bool UiDriver_SaveConfiguration(void);

static void UiDriver_DisplayRttySpeed(bool encoder_active);
static void UiDriver_DisplayRttyShift(bool encoder_active);
static void UiDriver_DisplayPskSpeed(bool encoder_active);
static inline void UiDriver_FButton_F4ActiveVFO(void);
static inline void UiDriver_FButton_F5Tune(void);
static void UiAction_ShowMems(void);
#ifdef SDR_AMBER
static void UiDriver_ShowGreeting(uint8_t variant);
#endif
static void UiDriver_LoadXVTR1(void);
static void UiDriver_LoadXVTR2(void);
static void UiDriver_LoadXVTR3(void);
static void UiDriver_LoadXVTR4(void);
static void UiDriver_StoreXVTR1(void);
static void UiDriver_StoreXVTR2(void);
static void UiDriver_StoreXVTR3(void);
static void UiDriver_StoreXVTR4(void);
static void UiAction_ToggleListXVTR(void);
static void UiAction_OffXVTR(void);
static uint8_t UiDriver_GetModeCode(void);
static void UiDriver_LoadBwData(void);
static void UiDriver_ChoiceKeyer(void);
static void UiDriver_ChoiceExt(void);
static void UiAction_ChoiceMems(void);
static void UiDriver_ChoiceXvtr(void);
static void UiDriver_ChoiceNormal(void);

#ifdef HELP_MENU
static void UiDriver_ShowHelp(void);
#endif

// encoder one
typedef enum {
    ENC_ONE_MODE_AUDIO_GAIN  = 0,
    ENC_ONE_MODE_RTTY_SPEED,
    ENC_ONE_MODE_ST_GAIN,
    ENC_ONE_MODE_CMP_LEVEL,
    ENC_ONE_MODE_PWR_GAIN,
    ENC_ONE_NUM_MODES
} EncoderOneModes;
//
// encoder two
typedef enum {
    ENC_TWO_MODE_RF_GAIN =      0,
    ENC_TWO_MODE_RTTY_SHIFT,
    ENC_TWO_MODE_SIG_PROC,
    ENC_TWO_MODE_NR,
    ENC_TWO_MODE_NOTCH_F,
    ENC_TWO_MODE_PEAK_F,
    ENC_TWO_MODE_BASS_GAIN,
    ENC_TWO_MODE_TREBLE_GAIN,
    ENC_TWO_NUM_MODES
} EncoderTwoModes;
//
// encoder three
typedef enum {
    ENC_THREE_MODE_RIT =            0,
    ENC_THREE_MODE_CW_SPEED,
    ENC_THREE_MODE_INPUT_CTRL,
    ENC_THREE_MODE_PSK_SPEED,
    ENC_THREE_NUM_MODES
} EncoderThreeModes;


// Tuning steps
const ulong tune_steps[T_STEP_MAX_STEPS] =
{
		T_STEP_1HZ,
		T_STEP_5HZ,
		T_STEP_10HZ,
		T_STEP_50HZ,
		T_STEP_100HZ,
		T_STEP_500HZ,
		T_STEP_1KHZ,
		T_STEP_5KHZ,
		T_STEP_9KHZ,
		T_STEP_10KHZ,
		T_STEP_100KHZ,
		T_STEP_1MHZ,
		T_STEP_10MHZ
};


// The following are calibrations for the S-meter based on 6 dB per S-unit, 10 dB per 10 dB mark above S-9
// The numbers within are linear gain values, not logarithmic, starting with a zero signal level of 1
// There are 33 entries, one corresponding with each point on the S-meter
#define	S_Meter_Cal_Size	33	// number of entries in table below
const float S_Meter_Cal[S_Meter_Cal_Size] =
{
		14.1,	    //1.41,	        //1, S0.5, 3dB
		20,		    //2,		    //2, S1, 6dB
		28.1,	    //2.81,	        //3, S1.5, 9dB
		30,		    //3,		    //4, S2, 12dB
		56.2,	    //5.62,	        //5, S2.5, 15dB
		79.4,	    //7.94,	        //6, S3, 18dB
		112.2,	    //11.22,	    //7, S3.5, 21dB
		158.5,	    //15.85,	    //8, S4, 24dB
		223.9,	    //22.39,	    //9, S4.5, 27dB
		316.3,	    //31.63,	    //10, S5, 30dB
		446.7,	    //44.67,	    //11, S5.5, 33dB
		631,	    //63.10,	    //12, S6, 36dB
		891.3,	    //89.13,	    //13, S6.5, 39dB
		1258.9,	    //125.89,	    //14, S7, 42dB
		1778.3,	    //177.83,	    //15, S7.5, 45dB
		2511.9,	    //251.19,	    //16, S8, 48dB
		3548.1,	    //354.81,	    //17, S8.5, 51dB
		5011.9,	    //501.19,	    //18, S9, 54dB
		8912.5,	    //891.25,	    //19, +5, 59dB
		15848.9,	//1584.89,	    //20, +10, 64dB
		28183.8,	//2818.38,	    //21, +15, 69dB
		50118.7,	//5011.87,	    //22, +20, 74dB
		89125.1,	//8912.51,	    //23, +25, 79dB
		158489.3,	//15848.93,	    //24, +30, 84dB
		281838.2,	//28183.82,	    //25, +35, 89dB
		501187.2,	//50118.72,	    //26, +40, 94dB
		891250.9,	//89125.09,	    //27, +45, 99dB
		1585893.2,	//158489.32,	//28, +50, 104dB
		2818382.9,	//281838.29,	//29, +55, 109dB
		5011872.3,	//501187.23,	//30, +60, 114dB
		8912509.4,	//891250.94,	//31, +65, 119dB
		15848931.9,	//1584893.19,	//32, +70, 124dB
		28183829.3,	//2818382.93	//33, +75, 129dB
};

// if S-meter is based on FFT / dBm calculation
const float S_Meter_Cal_dbm[S_Meter_Cal_Size] =
{
		// dBm vs. S-value
		-124.0,	// S0.5
		-121.0,	// S1
		-118.0,	// S1.5
		-115.0,	// S2
		-112.0,	// S2.5
		-109.0,	// S3
		-106.0,	// S3.5
		-103.0,	// S4
		-100.0,	// S4.5
		-97.0,	// S5
		-94.0,	// S5.5
		-91.0,	// S6
		-88.0,	// S6.5
		-85.0,	// S7
		-82.0,	// S7.5
		-79.0,	// S8
		-76.0,	// S8.5
		-73.0,	// S9
		-68.0,	// S9+5
		-63.0,	// +10
		-58.0,	// +15
		-53.0,	// +20
		-48.0,	// +25
		-43.0,	// +30
		-38.0,	// +35
		-33.0,	// +40
		-28.0,	// +45
		-23.0,	// +50
		-18.0,	// +55
		-13.0,	// +60
		-8.0,	// +65
		-3.0,	// +70
		2.0,    // +75
};

BandRegs vfo[VFO_MAX];

// ------------------------------------------------
// Keypad state
__IO KeypadState				ks;

// ------------------------------------------------
// Power supply meter
PowerMeter					pwmt;


// ------------------------------------------------

uchar drv_state = 0;
ui_driver_mode_t ui_driver_state = { .dmod_mode = 255, .digital_mode = 255 }; // initialize so that at startup we detect change
bool filter_path_change = false;

// check if touched point is within rectangle of valid action
bool UiDriver_CheckTouchRegion(const UiArea_t* tr_p)
{
	return ((ts.tp->hr_x <= (tr_p->x+tr_p->w)) &&
				(ts.tp->hr_x >= (tr_p->x)) &&
				(ts.tp->hr_y <= (tr_p->y+tr_p->h))) &&
				(ts.tp->hr_y >= (tr_p->y));

}


int32_t change_and_limit_int(volatile int32_t val, int32_t change, int32_t min, int32_t max)
{
	val +=change;
	if (val< min)
	{
		val = min;
	}
	else if (val>  max)
	{
		val = max;
	}
	return val;
}


uint32_t change_and_limit_uint(volatile uint32_t val, int32_t change, uint32_t min, uint32_t max)
{
	if (change < 0 && ( -change  > (val - min)))
	{
		val = min;
	}
	else if (change > 0 && change >  max - val)
	{
		val = max;
	}
	else
	{
		val +=change;
	}
	return val;
}

uint32_t change_and_wrap_uint(volatile uint32_t val, int32_t change, uint32_t min, uint32_t max)
{
	if (change  > ((int32_t)max - (int32_t)val))
	{
		val = min;
	}
	else if ((change + (int32_t)val) <  (int32_t)min)
	{
		val = max;
	}
	else
	{
		val +=change;
	}
	return val;
}

void incr_wrap_uint8(volatile uint8_t* ptr, uint8_t min, uint8_t max )
{
	*ptr = (change_and_wrap_uint(*ptr,+1,min,max))&0xff;
}

void incr_wrap_uint16(volatile uint16_t* ptr, uint16_t min, uint16_t max )
{
	*ptr = (change_and_wrap_uint(*ptr,+1,min,max))&0xff;
}

void decr_wrap_uint8(volatile uint8_t* ptr, uint8_t min, uint8_t max )
{
	*ptr = (change_and_wrap_uint(*ptr,-1,min,max))&0xff;
}

void decr_wrap_uint16(volatile uint16_t* ptr, uint16_t min, uint16_t max )
{
	*ptr = (change_and_wrap_uint(*ptr,-1,min,max))&0xff;
}

bool is_touchscreen_pressed()
{
	return (ts.tp->state == TP_DATASETS_VALID);	// touchscreen data available
}

bool is_vfo_b()
{
	return (ts.vfo_mem_mode & VFO_MEM_MODE_VFO_B) != 0;
}

vfo_name_t get_active_vfo()
{
    return is_vfo_b()?VFO_B:VFO_A;
}
// FIXME: The DSP stuff should go in a separate file
/**
 *
 * @return true if the noise blanker is enabled (if if turned "off)
 */
bool is_dsp_nb()
{
	return (ts.dsp.active & DSP_NB_ENABLE) != 0;
//	return (ts.nb_setting > 0); // noise blanker ON
}

/**
 *
 * @return true if the noise blanker settings indicate it should process things
 */
bool is_dsp_nb_active()
{
    return is_dsp_nb() && (ts.dsp.nb_setting > 0);
}

bool is_dsp_nr()
{
	return (ts.dsp.active & DSP_NR_ENABLE) != 0;
}

bool is_dsp_anr()
{
    return (ts.dsp.active & DSP_ANR_ENABLE) != 0;
}

bool is_dsp_nr_postagc()
{
	return (ts.dsp.active & DSP_NR_POSTAGC_ENABLE) != 0;
}

bool is_dsp_notch()
{
	return (ts.dsp.active & DSP_NOTCH_ENABLE) != 0;
}

bool is_dsp_mnotch()
{
	return (ts.dsp.active & DSP_MNOTCH_ENABLE) != 0;
}

bool is_dsp_mpeak()
{
	return (ts.dsp.active & DSP_MPEAK_ENABLE) != 0;
}

#define KEYACTION_NOP    (NULL)				// This action for the pressed key is treated as being executed, but it is a no-operation
#define KEYACTION_PASS ((void(*)())-1)		// This action for the pressed key is treated as not present, i.e. we do not report the key event has been processed

typedef struct
{
	uint32_t key_code;
	// use KEYACTION_NONE and KEYACTION_PASS to handled nop and pass for further processing,
	// see comments for these constants
	void (*press_func)(); // executed if short press of key is detected
	void (*hold_func)();  // executed if press and hold of key is detected
} keyaction_descr_t;

typedef struct
{
	const keyaction_descr_t* actions;
	int32_t size;
} keyaction_list_descr_t;

// Contest mode - F4IYT
static void UiDriver_RstCq(void)
{
	ts.pos_id_contest = 0;
	ts.pos_id_contesttry = 0;
	for (int idpos = 0; idpos <= ts.maxi_pos_contest; idpos++)
	{
		ts.matrix_contest[idpos] = 0;
	}
	for (int idpos = 0; idpos <= ts.maxi_pos_contesttry; idpos++)
	{
		ts.matrix_contesttry[idpos] = 0;
	}
    uint32_t  cq_color;
    UiMenu_MapColors(0,NULL, &cq_color);
	UiDriver_UpdateLcdFreq(RadioManagement_GetRXDialFrequency() ,cq_color, UFM_SECONDARY);
}
//
static void UiDriver_MemCq(void)
{
	int CqVal = 0;
    uint32_t freq_calc = RadioManagement_GetRXDialFrequency() + (ts.dmod_mode == DEMOD_CW ? RadioManagement_GetCWDialOffset() : 0 );      // get current tune frequency in Hz
	for (int idpos = 0; idpos <= ts.pos_id_contest; idpos++)
	{
		if (ts.matrix_contest[idpos] == freq_calc)
		{
			CqVal++;
			break;
    	}
    }
    if (!CqVal)
    {
		ts.matrix_contest[ts.pos_id_contest] = freq_calc;

		if (ts.pos_id_contest < ts.maxi_pos_contest)
		{
			ts.pos_id_contest ++;
		}
		for (int idpos = 0; idpos <= ts.pos_id_contesttry; idpos++)
		{
			if ((ts.matrix_contesttry[idpos] <= (freq_calc + 50)) && (ts.matrix_contesttry[idpos] >= (freq_calc - 50)))
			{
				ts.matrix_contesttry[idpos] = 0;
				break;
	    	}
	    }
	}
//    uint32_t  cq_color;
//    UiMenu_MapColors(ts.cq_colour,NULL, &cq_color);
//	UiDriver_UpdateLcdFreq(RadioManagement_GetRXDialFrequency() ,cq_color, UFM_SECONDARY);
    UiDriver_UpdateLcdFreq(RadioManagement_GetRXDialFrequency() ,Red, UFM_SECONDARY);
}
//
static void UiDriver_TryCq(void)
{
	int CqVal = 0;
    uint32_t freq_calc = RadioManagement_GetRXDialFrequency() + (ts.dmod_mode == DEMOD_CW ? RadioManagement_GetCWDialOffset() : 0 );      // get current tune frequency in Hz
	for (int idpos = 0; idpos <= ts.pos_id_contesttry; idpos++)
	{
		if (ts.matrix_contesttry[idpos] == freq_calc)
		{
			CqVal++;
			break;
    	}
    }
    if (!CqVal)
    {
		ts.matrix_contesttry[ts.pos_id_contesttry] = freq_calc;
		if (ts.pos_id_contesttry < ts.maxi_pos_contesttry)
		{
			ts.pos_id_contesttry ++;
		}
	}

	UiDriver_UpdateLcdFreq(RadioManagement_GetRXDialFrequency() ,Green, UFM_SECONDARY);
}
//
static void UiAction_ShowCq()
{
	uint8_t x_interval = 0;
	uint8_t y_start = 137; //146;
	uint8_t y_interval = 11;
	if (ts.swtcqtry==0)
	{
		char txt_cell[24];
		int idcpt = 7;
		int icpt = 0;
		for (int idpos = 0; idpos <= ts.pos_id_contest; idpos++)
		{
			if((idpos == 8) || (idpos == 9))
			{
				snprintf(txt_cell,20,"M%1u =%8lu ",idpos,ts.matrix_contest[idpos]);
			}
			else
			{
				snprintf(txt_cell,20,"M%1u=%8lu ",idpos,ts.matrix_contest[idpos]);
			}
			UiLcdHy28_PrintText(10+(x_interval),y_start+(icpt*y_interval), txt_cell,Red,Black,0);
			icpt++;
			if ( idpos >= idcpt )
			{
				icpt = 0;
				idcpt = idcpt + 8;
				x_interval = x_interval + 93 + (idpos >= 15 ? 7 : 0);
			}
		}
		ts.swtcqtry++;
	}
	else
	{
		char txt_cell2[24];
		int idcpt = 7;
		int icpt = 0;
		for (int idpos = 0; idpos <= ts.pos_id_contesttry; idpos++)
		{
			if((idpos == 8) || (idpos == 9))
			{
				snprintf(txt_cell2,20,"M%1u =%8lu ",idpos,ts.matrix_contesttry[idpos]);
			}
			else
			{
				snprintf(txt_cell2,20,"M%1u=%8lu ",idpos,ts.matrix_contesttry[idpos]);
			}
			UiLcdHy28_PrintText(10+(x_interval),y_start+(icpt*y_interval), txt_cell2,Green,Black,0);
			icpt++;
			if ( idpos >= idcpt )
			{
				icpt = 0;
				idcpt = idcpt + 8;
				x_interval = x_interval + 93 + (idpos >= 15 ? 7 : 0);
			}
		}
		ts.swtcqtry = 0;
	}
 }
// Contest mode - end

/*
 * @brief find the matching region in a list of region and associated function
 * @returns: true, if a match for the touch coordinates region was found.
 */
bool UiDriver_ProcessTouchActions(const touchaction_list_descr_t* tld, bool is_long_press)
{
	bool retval = false;
	if (tld != NULL)
	{
		for (uint32_t idx = 0; idx < tld->size; idx++)
		{
			if (UiDriver_CheckTouchRegion(&tld->actions[idx].region))
			{
			    if (is_long_press)
			    {
			        if (tld->actions[idx].function_long_press != NULL)
			        {
			            (*tld->actions[idx].function_long_press)();
			        }
			    }
			    else
			    {
			        if (tld->actions[idx].function_short_press != NULL)
			        {
			            (*tld->actions[idx].function_short_press)();
			        }
			    }
			    retval = true;
			    break;
			}
		}
	}
	return retval;
}

/*
 * @brief find the matching keycode in a list of keycodes and associated functions
 * @returns: true, if a match for the keycode was found and a function (which can be a "no operation") has been executed.
 */

bool UiDriver_ProcessKeyActions(const keyaction_list_descr_t* kld)
{
	bool retval = false;
	if (kld != NULL)
	{
		for (uint32_t idx = 0; idx < kld->size; idx++)
		{
			if (kld->actions[idx].key_code == ks.button_id)
			{
				void (*func_ptr)() =  ks.press_hold == true ? kld->actions[idx].hold_func:kld->actions[idx].press_func;
				if (func_ptr != KEYACTION_NOP && func_ptr != KEYACTION_PASS)
				{
					(*func_ptr)();
				}
				retval = func_ptr != KEYACTION_PASS;
				break;
			}
		}
	}

	return retval;
}

static void UiDriver_DisplayMessageStart(void)
{
    UiSpectrum_Clear();
}

static void UiDriver_DisplayMessageStop(void)
{
    if(ts.menu_mode)
    {
        UiMenu_RenderMenu(MENU_RENDER_ONLY);    // update menu display, was destroyed by message
    }
    else
    {
        UiSpectrum_Init();          // not in menu mode, redraw spectrum scope
    }
}


/**
 * @brief restarts lcd blanking timer, called in all functions which detect user interaction with the device
 */
void UiDriver_LcdBlankingStartTimer()
{
	if(ts.lcd_backlight_blanking & LCD_BLANKING_ENABLE)     // is LCD blanking enabled?
	{
		uint32_t ltemp = (ulong)(ts.lcd_backlight_blanking & LCD_BLANKING_TIMEMASK);      // get setting of LCD blanking timing
		ltemp *= 100;       // multiply to convert to deciseconds
		ts.lcd_blanking_time = ltemp + ts.sysclock;     // calculate future time at which LCD is to be turned off
		ts.lcd_blanking_flag = false;       // clear flag to make LCD turn on
	}
}

static void   UiDriver_LcdBlankingProcessTimer(void)
{
	// Process LCD auto-blanking
	if(ts.lcd_backlight_blanking & LCD_BLANKING_ENABLE)      // is LCD auto-blanking enabled?
	{
		if(ts.sysclock > ts.lcd_blanking_time)      // has the time expired and the LCD should be blanked?
		{
			ts.lcd_blanking_flag = true;             // yes - blank the LCD
		}
		else                                        // time not expired
		{
			ts.lcd_blanking_flag = false;             // un-blank the LCD
		}
	}
	else                                  // auto-blanking NOT enabled
	{
		ts.lcd_blanking_flag = false;               // always un-blank the LCD in this case
	}
}

static char ui_txt_msg_buffer[ui_txt_msg_buffer_size];

static int ui_txt_msg_idx= 0;
static bool ui_txt_msg_update = false;

void UiDriver_RIT_Reset()
{
	ts.rit_value = 0;
	UiDriver_UpdateDisplayAfterParamChange();
}

void UiDriver_TextMsgClear()
{
	uint32_t fillcnt;
	for(fillcnt=0; fillcnt<ts.Layout->TextMsg_buffer_max;fillcnt++)
	{
		ui_txt_msg_buffer[fillcnt]=' ';
	}
	ui_txt_msg_buffer[fillcnt]='\0';

	// FIXME there is more affective way to clear space on the screen.
//  UiLcdHy28_PrintText(ts.Layout->TextMsgLine.x,ts.Layout->TextMsgLine.y, ui_txt_msg_buffer,Yellow,Black,ts.Layout->TextMsg_font);
    UiLcdHy28_PrintText(ts.Layout->TextMsgLine.x,ts.Layout->TextMsgLine.y, ui_txt_msg_buffer,sd.txtline_colour,Black,ts.Layout->TextMsg_font);
    ui_txt_msg_idx = 0;
    ui_txt_msg_update = true;
}

void UiDriver_TextMsgDisplay()
{
    if (ui_txt_msg_update == true)
    {
        ui_txt_msg_update = false;
        if(ui_txt_msg_idx==0)
        {
        	uint32_t fillcnt;
        	for(fillcnt=0; fillcnt<ts.Layout->TextMsg_buffer_max;fillcnt++)
        	{
        		ui_txt_msg_buffer[fillcnt]=' ';
        	}
        	ui_txt_msg_buffer[fillcnt]='\0';
        }

//      UiLcdHy28_PrintText(ts.Layout->TextMsgLine.x,ts.Layout->TextMsgLine.y, ui_txt_msg_buffer,Yellow,Black,ts.Layout->TextMsg_font);
        UiLcdHy28_PrintText(ts.Layout->TextMsgLine.x,ts.Layout->TextMsgLine.y, ui_txt_msg_buffer,sd.txtline_colour,Black,ts.Layout->TextMsg_font);
    }
}

void UiDriver_TextMsgPutChar(char ch)
{
    if (ch=='\n' || ch == '\r')
    {
        ui_txt_msg_idx=0;
    	ui_txt_msg_buffer[ui_txt_msg_idx] = '\0';
    }
    else if (ui_txt_msg_idx < (ts.Layout->TextMsg_buffer_max))
    {
        ui_txt_msg_idx++;
    	ui_txt_msg_buffer[ui_txt_msg_idx] = '\0'; // set the line end before we add the character prevents unterminated strings
        ui_txt_msg_buffer[ui_txt_msg_idx-1]=ch; //fill from left to right
    }
    else
    {
        for (int shift_count = 0;shift_count < (ts.Layout->TextMsg_buffer_max-1);shift_count++)
        {
            ui_txt_msg_buffer[shift_count]=ui_txt_msg_buffer[shift_count+1];
        }
        ui_txt_msg_buffer[ts.Layout->TextMsg_buffer_max-1]=ch;
    }
    ui_txt_msg_update = true;
}

void UiDriver_TextMsgPutSign(const char *s)
{
	UiDriver_TextMsgPutChar('<');
	UiDriver_TextMsgPutChar(s[0]);
	UiDriver_TextMsgPutChar(s[1]);
	UiDriver_TextMsgPutChar('>');
}

static void UiDriver_LeftBoxDisplay(const uint8_t row, const char *label, bool encoder_active,
		const char* text, uint32_t color, uint32_t clr_val, bool text_is_value)
{

	uint32_t label_color = encoder_active?Black:color;

	// max visibility of active element
//	uint32_t bg_color = encoder_active?Orange:Blue;
//	uint32_t brdr_color = encoder_active?Orange:Blue;
	uint32_t bg_color = encoder_active?Orange:sd.boxes_colour;
	uint32_t brdr_color = encoder_active?Orange:sd.boxes_colour;

	uint8_t row_t = row;
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
	if(ts.show_wide_spectrum) // first filter box
	{
		row_t = (row==1)?0:1;
	}
	#else
	row_t = (row==1)?0:1;
	#endif
#else
	row_t = (row==1)?0:1;
#endif

	uint16_t posX, posY;
	if(ts.Layout->LEFTBOXES_MODE==MODE_HORIZONTAL)
	{
//		posX=ts.Layout->LEFTBOXES_IND.x+ (row * ts.Layout->LEFTBOXES_IND.w);
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
		posX=ts.Layout->LEFTBOXES_IND.x + (row_t * ts.Layout->LEFTBOXES_IND.w + ((ts.show_wide_spectrum)&(row_t>0)?2:0));
	#else
		posX=ts.Layout->LEFTBOXES_IND.x + (row_t * ts.Layout->LEFTBOXES_IND.w) + (row_t>0?2:0);
	#endif
#else
		posX=ts.Layout->LEFTBOXES_IND.x + (row_t * ts.Layout->LEFTBOXES_IND.w) + (row_t>0?2:0);
#endif
		posY=ts.Layout->LEFTBOXES_IND.y;
	}
	else
	{
		posX=ts.Layout->LEFTBOXES_IND.x;
		posY=ts.Layout->LEFTBOXES_IND.y + (row_t * ts.Layout->LEFTBOXES_IND.h);
	}


	UiLcdHy28_DrawEmptyRect(posX, posY, ts.Layout->LEFTBOXES_IND.h - 2, ts.Layout->LEFTBOXES_IND.w - 2, brdr_color);
	UiLcdHy28_PrintTextCentered(posX + 1, posY + 1,ts.Layout->LEFTBOXES_IND.w - 3, label,
			label_color, bg_color, 0);

	// this causes flicker, but I am too lazy to fix that now
	UiLcdHy28_DrawFullRect(posX + 1, posY + 1 + 12, ts.Layout->LEFTBOXES_IND.h - 4 - 11, ts.Layout->LEFTBOXES_IND.w - 3, text_is_value?Black:bg_color);
	if (text_is_value)
	{
		UiLcdHy28_PrintTextRight((posX + ts.Layout->LEFTBOXES_IND.w - 4), (posY + 1 + ts.Layout->LEFTBOXES_ROW_2ND_OFF), text,
				clr_val, text_is_value?Black:bg_color, 0);
	}
	else
	{
		UiLcdHy28_PrintTextCentered((posX + 1), (posY + 1 + ts.Layout->LEFTBOXES_ROW_2ND_OFF),ts.Layout->LEFTBOXES_IND.w - 3, text,
				color, bg_color, 0);
	}

}

static void UiDriver_LcdBlankingStealthSwitch(void)
{
	if(ts.lcd_backlight_blanking & LCD_BLANKING_ENABLE)
	{         // Yes - is MSB set, indicating "stealth" (backlight timed-off) mode?
		ts.lcd_backlight_blanking &= ~LCD_BLANKING_ENABLE;
	} // yes - clear that bit, turning off "stealth" mode
	else
	{
		if(ts.lcd_backlight_blanking & LCD_BLANKING_TIMEMASK)    // bit NOT set AND the timing set to NON-zero?
		{
			ts.lcd_backlight_blanking |= LCD_BLANKING_ENABLE;       // no - turn on MSB to activate "stealth" mode
		}
	}
}

void UiDriver_DisplayFilter()
{
//    if(ts.show_wide_spectrum)
//    {
//        return;
//    }
	const char* filter_ptr;
	uint32_t font_clr= filter_path_change?Black:White;

	const char *filter_names[2];

	AudioFilter_GetNamesOfFilterPath(ts.filter_path,filter_names);
	if (filter_names[1] != NULL)
	{
		filter_ptr = filter_names[1];
	}
	else
	{
		filter_ptr = " ";
	}

	UiDriver_LeftBoxDisplay(1,filter_names[0],filter_path_change,filter_ptr,font_clr, font_clr,false);
}

// TODO: most of this belongs to radio management, not UI
static void UiDriver_ToggleDigitalMode(void)
{
	if (ts.digital_mode != DigitalMode_None)
	{
		// a valid digital mode is set but may not be active yet
		if (ts.dmod_mode != DEMOD_DIGI)
		{
			if (RadioManagement_IsApplicableDemodMode(DEMOD_DIGI))
			{
				// this will switch to the corresponding sideband if we come from
				// SSB, otherwise the automatically selected default (AUTO LSB/USB ON) or the previously used
				// will be the selected one.
				if (is_ssb(ts.dmod_mode))
				{
					ts.digi_lsb = RadioManagement_LSBActive(ts.dmod_mode);
				}
				RadioManagement_SetDemodMode(DEMOD_DIGI);
			}
		}
	}
	else
	{
		if (ts.dmod_mode == DEMOD_DIGI)
		{
			// we are in digital mode but the current digital mode is in fact
			// None, i.e. we are going analog now
			RadioManagement_SetDemodMode(ts.digi_lsb?DEMOD_LSB:DEMOD_USB);
		}
	}
	UiDriver_UpdateDisplayAfterParamChange();
}

/*
 * @brief Function will update LO and Display Digits, it will never change LO if not necessary
 *
 * @param full_update set to true in order to have the full display digits being updated
 *
 */
void UiDriver_FrequencyUpdateLOandDisplay(bool full_update)
{
	if(is_splitmode())
	{
		// SPLIT mode
		UiDriver_UpdateFrequency(false,UFM_SMALL_TX);
		UiDriver_UpdateFrequency(false,UFM_SMALL_RX);
	}
	else
	{
		UiDriver_UpdateFrequency(false,UFM_AUTOMATIC);
	}
}

void UiDriver_DebugInfo_DisplayEnable(bool enable)
{

//	UiLcdHy28_PrintText(ts.Layout->DEBUG_X,ts.Layout->LOADANDDEBUG_Y,enable?"enabled":"       ",Green,Black,0);
	UiLcdHy28_PrintText(ts.Layout->DEBUG_X,ts.Layout->LOADANDDEBUG_Y,enable?"Debug->":"       ",Green,Black,0);

	if (enable == false)
	{
		UiLcdHy28_PrintText(ts.Layout->LOAD_X,ts.Layout->LOADANDDEBUG_Y,"     ",White,Black,0);
	}

	ts.show_debug_info = enable;

}

void UiDriver_SpectrumChangeLayoutParameters()
{
	UiSpectrum_WaterfallClearData();
	AudioDriver_SetProcessingChain(ts.dmod_mode, false);
	ts.iq_freq_delta = 0;

	if (ts.menu_mode == false)
	{
		UiSpectrum_Init();      // init spectrum scope
	}
}



/**
 * Sets a power level and updates the display accordingly.
 * @param power_level
 */
void UiDriver_HandlePowerLevelChange(const BandInfo* band, uint8_t power_level)
{
	if (RadioManagement_SetPowerLevel(band,power_level))
	{
		UiDriver_DisplayPowerLevel();
		if (ts.menu_mode)
		{
			UiMenu_RenderMenu(MENU_RENDER_ONLY);
		}
	}
}

void UiDriver_RefreshPowerLevel(const BandInfo* band, uint8_t power_level)
{
	UiDriver_HandlePowerLevelChange(band, power_level);
}

void UiDriver_HandleBandButtons(uint16_t button)
{

	static const bool BAND_DOWN = false;
	static const bool BAND_UP = true;

	bool buttondirSwap = (ts.flags1 & FLAGS1_SWAP_BAND_BTN)?true:false;

	bool dir;

	if (button == BUTTON_BNDM)
	{
		dir = buttondirSwap ? BAND_UP : BAND_DOWN;
	}
	else
	{
		dir = buttondirSwap ? BAND_DOWN : BAND_UP;
	}

	UiDriver_ChangeBand(dir);
}


static void UiDriver_PublicsInit(void)
{
	// Button state structure init state
	ks.button_id			= BUTTON_NONE;
	ks.button_pressed		= 0;
	ks.button_released		= 0;
	ks.button_processed		= 0;
	ks.debounce_time		= 0;


	// Auto button blink state
	//abst.blink_flag 		= 0;
	//abst.blink_skip 		= 0;

	// SWR meter init
	swrm.p_curr				= 0;
	swrm.fwd_calc			= 0;
	swrm.rev_calc			= 0;
	swrm.fwd_pwr			= 0;
	swrm.rev_pwr			= 0;
	swrm.fwd_dbm			= 0;
	swrm.rev_dbm			= 0;
	swrm.vswr			 	= 0;
	swrm.sensor_null		= SENSOR_NULL_DEFAULT;
	{
		int idx;
		for (idx = 0; idx < COUPLING_MAX; idx++)
		{
			swrm.coupling_calc[idx]    = SWR_COUPLING_DEFAULT;
		}
	}
	swrm.pwr_meter_disp		= 0;	// Display of numerical FWD/REV power metering off by default
	swrm.pwr_meter_was_disp = 0;	// Used to indicate if FWD/REV numerical power metering WAS displayed

	// Power supply meter
	pwmt.p_curr				= 0;
	pwmt.pwr_aver 			= 0;
	pwmt.undervoltage_detected = false;

}

static void UiDriver_DspModeMaskInit(void)
{
    if(mchf_touchscreen.present)
    {
        //preventing DSP functions mask to have not proper value
        if (ts.dsp.mode_mask == 0 || ts.dsp.mode_mask == 1)
        {
            // empty mask is invalid, set it to all entries enabled
            ts.dsp.mode_mask = DSP_SWITCH_MODEMASK_ENABLE_DEFAULT;
        }
        else
        {
            // just make sure DSP OFF is always on the list
            ts.dsp.mode_mask|=DSP_SWITCH_MODEMASK_ENABLE_DSPOFF;
        }

        ts.dsp.mode_mask&=DSP_SWITCH_MODEMASK_ENABLE_MASK;
    }
    else
    {
        ts.dsp.mode_mask=DSP_SWITCH_MODEMASK_ENABLE_DEFAULT;        //disable masking when no touchscreen controller detected
    }
}


void UiDriver_Init()
{
    // set the encoders to their default values
    ts.enc_one_mode     = ENC_ONE_MODE_AUDIO_GAIN;
    ts.enc_two_mode     = ENC_TWO_MODE_RF_GAIN;
    ts.enc_thr_mode     = ENC_THREE_MODE_RIT;

	// Driver publics init
	UiDriver_PublicsInit();
	// Init frequency publics
	UiDriver_InitFrequency();

	Keypad_Scan();

	// Load stored data from eeprom or calibrate touchscreen
	bool run_keytest = (UiDriver_LoadSavedConfigurationAtStartup() == false && UiDriver_TouchscreenCalibration() == false);


	if(mchf_touchscreen.present)
	{
		//Touchscreen calibration test.
		//We cannot distinguish when touchscreen is uncalibrated with other method than comparing calibration values to empty state of EEPROM (0xff).
	    //It would be nice if someone has better idea how to do it without digging into calibration matrix computation to describe the allowed range of coefficients. Feb 2018, SP9BSL.
		bool IS_TSCalibrated=0;
		for(int16_t m=0; m<6; m++)
		{
			IS_TSCalibrated|=ts.tp->cal[m]!=0xffffffff;
		}

		UiDriver_StartupScreen_LogIfProblem(IS_TSCalibrated == 0,
				"WARNING:  TOUCHSCREEN NOT CALIBRATED!!!\nRun calibration first!");
	}
	if (ts.special_functions_enabled != 1)
	{
//	  UiDriver_StartupScreen_LogIfProblem(AudioDriver_GetTranslateFreq() == 0,
		UiDriver_StartupScreen_LogIfProblem(ts.iq_freq_mode == FREQ_IQ_CONV_MODE_OFF,
			"WARNING:  Freq. Translation is OFF!!!\nTranslation is STRONGLY recommended!!");
	}

	// now run all inits which need to be done BEFORE going into test screen mode
	uint8_t mirtemp;
	if(ts.flags1 & FLAGS1_REVERSE_X_TOUCHSCREEN)
	{
		mirtemp = 1;
	}
	else
	{
		mirtemp = 0;
	}
	if(ts.flags1 & FLAGS1_REVERSE_Y_TOUCHSCREEN)
	{
		mirtemp += 2;
	}

	UiLcdHy28_TouchscreenInit(mirtemp);

	if (run_keytest)
	{
		UiDriver_KeyTestScreen();
	}


	osc->setPPM((float)ts.freq_cal/10.0);

	df.tune_new = vfo[get_active_vfo()].band[ts.band->band_mode].dial_value;		// init "tuning dial" frequency based on restored settings
	df.tune_old = 0; // with this we force a frequency change once the main loop becomes active

	ts.cw_lsb = RadioManagement_CalculateCWSidebandMode();			// determine CW sideband mode from the restored frequency


	UiDriver_DspModeMaskInit();

	sd.display_offset = INIT_SPEC_AGC_LEVEL;		// initialize setting for display offset/AGC

	// Reset inter driver requests flag
	ts.LcdRefreshReq	= 0;
	ts.new_band 		= ts.band->band_mode;
	df.step_new 		= df.tuning_step;

	// Extra HW init
	Board_PostInit();


	UiDriver_LcdBlankingStartTimer();			// init timing for LCD blanking
	ts.lcd_blanking_time = ts.sysclock + LCD_STARTUP_BLANKING_TIME;
	ts.low_power_shutdown_time = ts.sysclock + LOW_POWER_SHUTDOWN_DELAY_TIME;
	ts.cw_keyer_speed_bak = ts.cw_keyer_speed;
}

#define BOTTOM_BAR_LABEL_W (56)
#define POS_BOTTOM_BAR_F1_offset 2
void UiDriver_DrawFButtonLabel(uint8_t button_num, const char* label, uint32_t label_color)
{
	//UiLcdHy28_PrintTextCentered(BOTTOM_BAR_F1_X + (button_num - 1)*(POS_BOTTOM_BAR_BUTTON_W+2), POS_BOTTOM_BAR_F1_Y, BOTTOM_BAR_LABEL_W, label,
//	UiLcdHy28_PrintTextCentered(ts.Layout->BOTTOM_BAR.x+POS_BOTTOM_BAR_F1_offset + (button_num - 1)*(ts.Layout->BOTTOM_BAR.w+2), ts.Layout->BOTTOM_BAR.y, BOTTOM_BAR_LABEL_W, label,
//			label_color, Black, 0);
    UiLcdHy28_PrintTextCentered(ts.Layout->BOTTOM_BAR.x+POS_BOTTOM_BAR_F1_offset + (button_num - 1)*(ts.Layout->BOTTOM_BAR.w+2), ts.Layout->BOTTOM_BAR.y-2, BOTTOM_BAR_LABEL_W, label,
            label_color, Black, 0);
}

void UiDriver_EncoderDisplay(const uint8_t row, const uint8_t column, const char *label, bool encoder_active,
		const char temp[5], uint32_t color)
{

	uint32_t label_color = encoder_active?Black:Grey1;

	// max visibility of active element
	uint32_t bg_color = encoder_active?Orange:Grey;
	uint32_t brdr_color = encoder_active?Orange:Grey;

	if(ts.Layout->ENCODER_MODE==MODE_HORIZONTAL)
	{
		UiLcdHy28_DrawEmptyRect(ts.Layout->ENCODER_IND.x + ENC_COL_W *2 * column + row *ENC_COL_W+column*Xspacing, ts.Layout->ENCODER_IND.y , ENC_ROW_H - 2, ENC_COL_W - 2, brdr_color);
		UiLcdHy28_PrintTextCentered((ts.Layout->ENCODER_IND.x + 1 + ENC_COL_W * 2 * column + row *ENC_COL_W+column*Xspacing), (ts.Layout->ENCODER_IND.y + 1),ENC_COL_W - 3, label,
				label_color, bg_color, 0);
		UiLcdHy28_PrintTextRight((ts.Layout->ENCODER_IND.x + ENC_COL_W - 4 + ENC_COL_W * 2 * column+ row *ENC_COL_W+column*Xspacing), (ts.Layout->ENCODER_IND.y + 1 + ENC_ROW_2ND_OFF), temp,
				color, Black, 0);
	}
//	else
	else if(!ts.show_wide_spectrum)
	{
		UiLcdHy28_DrawEmptyRect(ts.Layout->ENCODER_IND.x + ENC_COL_W * column, ts.Layout->ENCODER_IND.y + row * ENC_ROW_H, ENC_ROW_H - 2, ENC_COL_W - 2, brdr_color);
		UiLcdHy28_PrintTextCentered((ts.Layout->ENCODER_IND.x + 1 + ENC_COL_W * column), (ts.Layout->ENCODER_IND.y + 1 + row * ENC_ROW_H),ENC_COL_W - 3, label,
				label_color, bg_color, 0);
		UiLcdHy28_PrintTextRight((ts.Layout->ENCODER_IND.x + ENC_COL_W - 4 + ENC_COL_W * column), (ts.Layout->ENCODER_IND.y + 1 + row * ENC_ROW_H + ENC_ROW_2ND_OFF), temp,
				color, Black, 0);
	}
    else
    {
        uint8_t ENC_ROW_H_WS        = 27;
        uint8_t ENC_ROW_2ND_OFF_WS  = 12;

        UiLcdHy28_DrawEmptyRect(ts.Layout->ENCODER_IND.x + ENC_COL_W * column, ts.Layout->ENCODER_IND.y + row * ENC_ROW_H_WS, ENC_ROW_H_WS - 2, ENC_COL_W - 2, brdr_color);
        UiLcdHy28_PrintTextCentered((ts.Layout->ENCODER_IND.x + 1 + ENC_COL_W * column), (ts.Layout->ENCODER_IND.y + row * ENC_ROW_H_WS),ENC_COL_W - 3, label,
                label_color, bg_color, 0);
        UiLcdHy28_PrintTextRight((ts.Layout->ENCODER_IND.x + ENC_COL_W - 4 + ENC_COL_W * column), (ts.Layout->ENCODER_IND.y + 1 + row * ENC_ROW_H_WS + ENC_ROW_2ND_OFF_WS), temp,
                color, Black, 0);
    }
}

static uint8_t UiDriver_GetModeCode()
{
    uint8_t mode=0;

    switch(ts.dmod_mode)
    {
    case DEMOD_USB: mode = 0; break;
    case DEMOD_LSB: mode = 0; break;
    case DEMOD_SAM: mode = 2; break;
    case DEMOD_AM:  mode = 1; break;
    case DEMOD_FM:  mode = 4; break;
    case DEMOD_CW:  mode = 3; break;
    case DEMOD_DIGI:
        switch(ts.digital_mode)
        {
        case DigitalMode_RTTY:   mode = 5; break;
        case DigitalMode_BPSK:   mode = 6; break;
#ifdef USE_FREEDV
        case DigitalMode_FreeDV: mode = 7; break;
#endif
        }
        break;
#ifdef USE_TWO_CHANNEL_AUDIO
    case DEMOD_IQ:        mode = 8; break;
    case DEMOD_SSBSTEREO: mode = 9; break;
#endif
        default:
            break;
    }
    return mode;
}

void UiDriver_StoreBwData()
{
    uint8_t band = RadioManagement_GetBand(df.tune_new)->band_mode;
    uint8_t mode = UiDriver_GetModeCode();
    bw_data[mode][band] = ts.filter_path;
}

static void UiDriver_LoadBwData()
{
    uint8_t band = RadioManagement_GetBand(df.tune_new)->band_mode;
    uint8_t mode = UiDriver_GetModeCode();
    if(bw_data[mode][band]) // not empty
    {
    	ts.filter_path = bw_data[mode][band];
//    	UiDriver_UpdateDisplayAfterParamChange();
    }
}

static void UiDriver_StoreMemData(uint8_t mem_cell)
{
    uint8_t band = RadioManagement_GetBand(df.tune_new)->band_mode;
    uint8_t mode = 0;
    uint8_t sideband = 0;
    mem_cell--; // in argument using cell numder 1...4 for comfort ))

    switch(ts.dmod_mode)
    {
    case DEMOD_USB:
        mode = 0; sideband = 1;//"SSB-U"
        break;
    case DEMOD_LSB:
        mode = 0; sideband = 2;//"SSB-L"
        break;
    case DEMOD_SAM:
        if(ads.sam_sideband == SAM_SIDEBAND_LSB)
        {
            mode = 2; sideband = 2;//"SAM-L";
        }
        else if (ads.sam_sideband == SAM_SIDEBAND_USB)
        {
            mode = 2; sideband = 1;//"SAM-U";
        }
#ifdef USE_TWO_CHANNEL_AUDIO
        else if (ads.sam_sideband == SAM_SIDEBAND_STEREO)
        {
            mode = 2; sideband = 3;//"SAM-S";
        }
#endif
        else
        {
            mode = 2; sideband = 0;//"SAM";
        }
        break;
    case DEMOD_AM:
        mode = 1; sideband = 0;//"AM";
        break;
    case DEMOD_FM:
        mode = 4;//"FM"
        sideband = RadioManagement_FmDevIs5khz() ? 5 : 4; //"W"/"N"
        break;
    case DEMOD_CW:
        mode = 3;//"CW"
        sideband = ts.cw_lsb? 2: 1;//"L"/"U"
        break;
    case DEMOD_DIGI:
        sideband = ts.digi_lsb? 2: 1;//"L"/"U"
        switch(ts.digital_mode)
        {
        case DigitalMode_RTTY:
            mode = 5;//"RTTY"
            break;
        case DigitalMode_BPSK:
            mode = 6;//"BPSK"
            break;
#ifdef USE_FREEDV
        case DigitalMode_FreeDV:
            mode = 7;//"FreeDV"
            break;
#endif
        }
        break;
#ifdef USE_TWO_CHANNEL_AUDIO
    case DEMOD_IQ:
        mode = 8;//"IQ"
        sideband = 3;//"S";
        break;
    case DEMOD_SSBSTEREO:
        mode = 9;//"SSBSTEREO"
        sideband = 3;//"S";
        break;
#endif
        default:
            break;
    }
    mem_data[mem_cell][0][band] = df.tune_old;
    mem_data[mem_cell][1][band] = (ts.filter_path<<16)+(mode<<8)+sideband;
    if(ts.mem_disp)
    {
        UiAction_ShowMems();
    }
}

static void UiDriver_StoreMemData1(void)
{
//    UiDriver_StoreMemData(1);
	if(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON)
	{
		UiDriver_MemCq();
	}
	else
	{
	     UiDriver_StoreMemData(1);
	}
}

static void UiDriver_StoreMemData2(void)
{
//    UiDriver_StoreMemData(2);
	if(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON)
	{
		UiDriver_TryCq();
	}
	else
	{
		UiDriver_StoreMemData(2);
	}
}

static void UiDriver_StoreMemData3(void)
{
//    UiDriver_StoreMemData(3);
	if(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON)
	{
		UiDriver_RstCq();
	}
	else
	{
		UiDriver_StoreMemData(3);
	}
}

static void UiDriver_StoreMemData4(void)
{
//    UiDriver_StoreMemData(4);
	if(!(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON))
	{
		UiDriver_StoreMemData(4);
	}
}

static void UiDriver_LoadMemData(uint8_t mem_cell)
{
    mem_cell--; // in argument using cell numder 1...4 for comfort ))
    uint8_t band = RadioManagement_GetBand(df.tune_old)->band_mode;
    ts.dvmode = false;
    uint32_t freq = mem_data[mem_cell][0][band];
    if((ts.txrx_mode!=TRX_MODE_RX)||(freq==0)||(band!=RadioManagement_GetBand(freq)->band_mode)||(freq<500000)||(freq>55000000))
    {
        return; // empty/crash mem cell or TUNE/TX
    }
    // frequency
    df.tune_new = mem_data[mem_cell][0][band];
    bool result = RadioManagement_ChangeFrequency(true, df.tune_new, TRX_MODE_RX);
    if(!result)
    {
        return; // Don't changed freq, alarm!!!
    }
    // mode & sideband
    uint8_t mode = (mem_data[mem_cell][1][band] & 0x0000FF00)>>8;
    uint8_t sideband = mem_data[mem_cell][1][band] & 0x000000FF;
    if(mode==0)
        {ts.dmod_mode = sideband==1?DEMOD_USB:DEMOD_LSB;}
    else if(mode==1)
        {ts.dmod_mode = DEMOD_AM;}
    else if(mode==2)
    {
        ts.dmod_mode = DEMOD_SAM;
        if(sideband == 2)
        {
            ads.sam_sideband = SAM_SIDEBAND_LSB;
        }
        else if (sideband ==1)
        {
            ads.sam_sideband = SAM_SIDEBAND_USB;
        }
#ifdef USE_TWO_CHANNEL_AUDIO
        else if (sideband == 3)
        {
            ads.sam_sideband = SAM_SIDEBAND_STEREO;
        }
#endif
        else if (sideband == 0)
        {
            ads.sam_sideband = SAM_SIDEBAND_BOTH;
        }
    }
    else if(mode==3)
    {
        ts.dmod_mode = DEMOD_CW;
        ts.cw_lsb = sideband==2?true:false;
    }
    else if(mode==4)
    {
        ts.dmod_mode = DEMOD_FM;
        sideband = RadioManagement_FmDevIs5khz() ? 5 : 4; //"W"/"N"
        if(sideband == 5) // "W"
        {
            ts.flags2 = ts.flags2 | FLAGS2_FM_MODE_DEVIATION_5KHZ;
        }
        else if(sideband == 4) // "N"
        {
            ts.flags2 = ts.flags2 & ~FLAGS2_FM_MODE_DEVIATION_5KHZ;
        }
    }
    else if(mode==5)
    {
        ts.dmod_mode = DEMOD_DIGI;
        ts.digi_lsb = sideband==2?true:false;
        ts.digital_mode = DigitalMode_RTTY;
        ts.dvmode = true;
    }
    else if(mode==6)
    {
        ts.dmod_mode = DEMOD_DIGI;
        ts.digi_lsb = sideband==2?true:false;
        ts.digital_mode = DigitalMode_BPSK;
        ts.dvmode = true;
    }
#ifdef USE_FREEDV
    else if(mode==7)
    {
        ts.dmod_mode = DEMOD_DIGI;
        ts.digi_lsb = sideband==2?true:false;
        ts.digital_mode = DigitalMode_FreeDV;
        ts.dvmode = true;
    }
#endif
#ifdef USE_TWO_CHANNEL_AUDIO
    else if(mode==8)
    {
        ts.dmod_mode = DEMOD_IQ;
        ts.stereo_enable = true;
    }
    else if(mode==9)
    {
        ts.dmod_mode = DEMOD_SSBSTEREO;
        ts.stereo_enable = true;
    }
#endif
    RadioManagement_SetDemodMode(ts.dmod_mode);
    // filter
    ts.filter_path = (mem_data[mem_cell][1][band]>>16);
    // final
    if(ts.mem_disp)
    {
        ts.mem_disp = false;
    }
    UiDriver_UpdateDisplayAfterParamChange();
    UiSpectrum_Init();
}

static void UiDriver_LoadMemData1(void)
{
//    UiDriver_LoadMemData(1);
	if(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON)
	{
		UiDriver_MemCq();
	}
	else
	{
		UiDriver_LoadMemData(1);
	}
}

static void UiDriver_LoadMemData2(void)
{
//    UiDriver_LoadMemData(2);
	if(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON)
	{
		UiDriver_TryCq();
	}
	else
	{
		UiDriver_LoadMemData(2);
	}
}

static void UiDriver_LoadMemData3(void)
{
//    UiDriver_LoadMemData(3);
	if(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON)
	{
		UiDriver_RstCq();
	}
	else
	{
		UiDriver_LoadMemData(3);
	}
}

static void UiDriver_LoadMemData4(void)
{
//    UiDriver_LoadMemData(4);
	if(!(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON))
	{
		UiDriver_LoadMemData(4);
	}
}

static void UiAction_ShowMems()
{
//  ts.mem_disp = true;
    uint8_t band = RadioManagement_GetBand(df.tune_old)->band_mode;
//    uint16_t freq = 0;
    uint32_t freq = 0;//df.tune_old;
    int8_t mode = 0;
    uint8_t sideband = 0;
    char txt_cell[2];
    char txt_freq[10] = "???";
    char* txt_mode = "???";
    char* txt_sideband = "?";
    int16_t filter = 0;
    const char *filter_names[2];
    uint8_t y_start = 146;
    uint8_t y_interval = 20;

    for(int mem_cell = 0; mem_cell<4; mem_cell++)
    {
        snprintf(txt_cell,4,"M%1u",mem_cell+1);
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
        UiLcdHy28_PrintText(10+(!ts.show_wide_spectrum?70:0),y_start+(mem_cell*y_interval), txt_cell,Yellow,Black,0);
	#else
        UiLcdHy28_PrintText(10,y_start+(mem_cell*y_interval), txt_cell,Yellow,Black,0);
	#endif
#else
        UiLcdHy28_PrintText(10,y_start+(mem_cell*y_interval), txt_cell,Yellow,Black,0);
#endif

        freq = mem_data[mem_cell][0][band];
        if ((freq != 0) && (band == RadioManagement_GetBand(freq)->band_mode) && (freq > 500000) && (freq < 55000000))
        {
            mode = (mem_data[mem_cell][1][band]>>8) & 0xFF;
            sideband = mem_data[mem_cell][1][band] & 0xFF;
            filter = (mem_data[mem_cell][1][band]>>16) & 0xFFFF;
            AudioFilter_GetNamesOfFilterPath(filter,filter_names);
            switch(mode)
            {
            case 0:
                //txt_mode = "   SSB";
                break;
            case 1:
                txt_mode = "    AM";
                break;
            case 2:
                txt_mode = "   SAM";
                break;
            case 3:
                txt_mode = "    CW";
                break;
            case 4:
                txt_mode = "    FM";
                break;
            case 5:
                txt_mode = "  RTTY";
                break;
            case 6:
                txt_mode = "  BPSK";
                break;
            case 7:
                txt_mode = "FreeDV";
                break;
#ifdef USE_TWO_CHANNEL_AUDIO
            case 8:
                txt_mode = "    IQ";
                break;
            case 9:
                txt_mode = " SSB-S";
                break;
#endif
            default:
                break;
            }

            switch(sideband)
            {
            case 0:
                txt_sideband = " ";
                break;
            case 1:
                if(mode == 0)
                {
                    txt_mode = "   USB";
                    txt_sideband = " ";
                }
                else
                {
                    txt_sideband = "U";
                }
                break;
            case 2:
                if(mode == 0)
                {
                    txt_mode = "   LSB";
                    txt_sideband = " ";
                }
                else
                {
                    txt_sideband = "L";
                }
                break;
                break;
            case 3:
                txt_sideband = "S";
                break;
            case 4:
                txt_sideband = "N";
                break;
            case 5:
                txt_sideband = "W";
                break;
            default:
                break;
            }

            sprintf(txt_freq,"%8ld",freq);
            uint8_t i = 0;
            uint8_t q = 0;
            char symb[2] = " ";
            for(i = 0; i<8; i++) // formatted output of freq
            {
                symb[0] = txt_freq[i];
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
                if(i==2 || i==5)
                {
                    UiLcdHy28_PrintText(38+(!ts.show_wide_spectrum?70:0)+(q*8),y_start+(mem_cell*y_interval), ".",Yellow,Black,0);
                    q++;
                }
                UiLcdHy28_PrintText(38+(!ts.show_wide_spectrum?70:0)+(q*8),y_start+(mem_cell*y_interval), symb,Yellow,Black,0);
                q++;
            }
//          UiLcdHy28_PrintText(38,y_start+(mem_cell*y_interval), txt_freq,Yellow,Black,0);
            UiLcdHy28_PrintText(128+(!ts.show_wide_spectrum?70:0),y_start+(mem_cell*y_interval), txt_mode,Yellow,Black,0);
            UiLcdHy28_PrintText(188+(!ts.show_wide_spectrum?70:0),y_start+(mem_cell*y_interval), txt_sideband,Yellow,Black,0);
	#else
            if(i==2 || i==5)
            {
                UiLcdHy28_PrintText(38+(q*8),y_start+(mem_cell*y_interval), ".",Yellow,Black,0);
                q++;
            }
            UiLcdHy28_PrintText(38+(q*8),y_start+(mem_cell*y_interval), symb,Yellow,Black,0);
            q++;
        }
//          UiLcdHy28_PrintText(38,y_start+(mem_cell*y_interval), txt_freq,Yellow,Black,0);
        UiLcdHy28_PrintText(128,y_start+(mem_cell*y_interval), txt_mode,Yellow,Black,0);
        UiLcdHy28_PrintText(188,y_start+(mem_cell*y_interval), txt_sideband,Yellow,Black,0);
	#endif
#else
            if(i==2 || i==5)
            {
                UiLcdHy28_PrintText(38+(q*8),y_start+(mem_cell*y_interval), ".",Yellow,Black,0);
                q++;
            }
            UiLcdHy28_PrintText(38+(q*8),y_start+(mem_cell*y_interval), symb,Yellow,Black,0);
            q++;
        }
//          UiLcdHy28_PrintText(38,y_start+(mem_cell*y_interval), txt_freq,Yellow,Black,0);
        UiLcdHy28_PrintText(128,y_start+(mem_cell*y_interval), txt_mode,Yellow,Black,0);
        UiLcdHy28_PrintText(188,y_start+(mem_cell*y_interval), txt_sideband,Yellow,Black,0);
#endif

#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
            if(ts.show_wide_spectrum)
            {
	#endif
#endif
                UiLcdHy28_PrintText(206+(strlen(filter_names[0])!=5?8:0),y_start+(mem_cell*y_interval), filter_names[0],Yellow,Black,0);
                UiLcdHy28_PrintText(262,y_start+(mem_cell*y_interval), filter_names[1],Yellow,Black,0);
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
            }
	#endif
#endif
        }
        else
        {
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
            UiLcdHy28_PrintText(38+(!ts.show_wide_spectrum?70:0),y_start+(mem_cell*y_interval), "empty",White,Black,0);
	#else
            UiLcdHy28_PrintText(38,y_start+(mem_cell*y_interval), "empty",White,Black,0);
	#endif
#else
            UiLcdHy28_PrintText(38,y_start+(mem_cell*y_interval), "empty",White,Black,0);
#endif
        }
    }
}

void UiDriver_DisplayFButton_F1MenuExit()
{
	char* cap;
	uint32_t color;
	if (ts.keyer_mode.active)
	{
		if (ts.keyer_mode.button_recording == KEYER_BUTTON_1)
		{
			cap = "REC";
			color = Red;
		}
		else
		{
			cap = (char*) ts.keyer_mode.cap[0];
			color = (ts.expflags1 & EXPFLAGS1_FAST_MACRO_1_2)? Yellow : White;
		}
	}
	else if(ts.ExtFMenuActive)
	{
		cap = "RIT 0";
		color = White;
	}
	else if(ts.XvtrFMenuActive)
	{
		cap = "XVTR 1";
		color = White;
	}
	else if(ts.MenuOfFMenusActive)
	{
		cap = "MACROS";
		color = Green;
	}
	else
	{
		if (!ts.menu_var_changed)
		{

			if (ts.menu_mode)
			{
				cap = "EXIT";
				color = Yellow;
			}
			else
			{
				cap = "MENU";
				color = White;
			}
		}
		else
		{
			cap = ts.menu_mode?"EXIT *":"MENU *";
			color = Orange;
		}
	}
	UiDriver_DrawFButtonLabel(1, cap, color);
}


static void UiDriver_DisplayFButton_F2SnapMeter(void)
{
	const char* cap;
	uint32_t color;
	if (ts.keyer_mode.active)
	{
		if (ts.keyer_mode.button_recording == KEYER_BUTTON_2)
		{
			cap = "REC";
			color = Red;
		}
		else
		{
			cap = (char *)ts.keyer_mode.cap[1];
            color = (ts.expflags1 & EXPFLAGS1_FAST_MACRO_1_2)? Yellow : White;
		}
	}
	else if(ts.ExtFMenuActive)
	{
		cap = "ZOOM-";
		color = White;
	}
	else if(ts.XvtrFMenuActive)
	{
		cap = "XVTR 2";
		color = White;
	}
    else if(ts.MenuOfFMenusActive)
    {
        cap = "EXT";
        color = Green;
    }
	else
	{

		cap = "SNAP";
		color = White;    // yes - indicate with color
//		color = White;
//		cap = "METER";
	}
	UiDriver_DrawFButtonLabel(2,cap,color);
}



static void UiDriver_FButton_F3MemSplit(void)
{
	const char* cap;
	uint32_t color;

	if (ts.keyer_mode.active)
	{
		if (ts.keyer_mode.button_recording == KEYER_BUTTON_3)
		{
			cap = "REC";
			color = Red;
		}
		else
		{
			cap = (char *)ts.keyer_mode.cap[2];
			color = White;
		}

	}
	else if(ts.ExtFMenuActive)
	{
		cap = "ZOOM+";
		color = White;
	}
	else if(ts.XvtrFMenuActive)
	{
		cap = "LST/OFF";
		color = Blue2;
	}
    else if(ts.MenuOfFMenusActive)
    {
        cap = "MEMS";
        color = Green;
    }
	else
	{
		if (ts.vfo_mem_flag)            // is it in VFO MEM mode now?
		{
//			cap = "MEM";
////			color = White;    // yes - indicate with color
//			color = Yellow;
//			UiDriver_DrawFButtonLabel(1, "M1", White);
//			UiDriver_DrawFButtonLabel(2, "M2", White);
//			UiDriver_DrawFButtonLabel(4, "M3", White);
//			UiDriver_DrawFButtonLabel(5, "M4", White);
			if(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON)
			{
				cap = "LIST";
				color = Yellow;
				UiDriver_DrawFButtonLabel(1, "MEM", Red);
				UiDriver_DrawFButtonLabel(2, "TRY", Green);
				UiDriver_DrawFButtonLabel(4, "RST", White);
				UiDriver_DrawFButtonLabel(5, "-", White);
			}
			else
			{
				cap = "MEM";
				color = Yellow;
				UiDriver_DrawFButtonLabel(1, "M1", White);
				UiDriver_DrawFButtonLabel(2, "M2", White);
				UiDriver_DrawFButtonLabel(4, "M3", White);
				UiDriver_DrawFButtonLabel(5, "M4", White);
			}
		}
		else
		{
			color = is_splitmode() ?
					SPLIT_ACTIVE_COLOUR : SPLIT_INACTIVE_COLOUR;
			cap = "SPLIT";
			UiDriver_DisplayFButton_F1MenuExit();
			UiDriver_DisplayFButton_F2SnapMeter();
			UiDriver_FButton_F4ActiveVFO();
			UiDriver_FButton_F5Tune();
		}
	}
	UiDriver_DrawFButtonLabel(3, cap, color);
}


static inline void UiDriver_FButton_F4ActiveVFO()
{
	const char* cap;
	uint32_t color;
    if (ts.keyer_mode.active)
    {
        if (ts.keyer_mode.button_recording == KEYER_BUTTON_4)
        {
            cap = "REC";
            color = Red;
        }
        else
        {
            cap = (char *)ts.keyer_mode.cap[3];
            color = White;
        }

    }
	else if(ts.ExtFMenuActive)
	{
#ifdef SDR_AMBER
		cap = "RXIN-";
		color = White;
#else
		cap = "TXINP";
		color = White;
#endif
	}
    else if(ts.vfo_mem_flag)
    {
//		cap = "M3";
		color = White;
		if(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON)
		{
			cap = "RST";
		}
		else
		{
			cap = "M3";
		}
    }
    else if(ts.XvtrFMenuActive)
    {
		cap = "XVTR 3";
		color = White;
    }
    else if(ts.MenuOfFMenusActive)
    {
        cap = "XVTR";
        color = Green;
    }
	else
	{
		cap = is_vfo_b() ? "VFO B" : "VFO A";
		color = White;
	}
	UiDriver_DrawFButtonLabel(4, cap, color);
}

static inline void UiDriver_FButton_F5Tune()
{
	const char* cap="";
//	uint32_t color;
//	color = RadioManagement_IsTxDisabled() ? Grey1 : (ts.tune ? Red : White);
	uint32_t color = RadioManagement_IsTxDisabled() ? Grey1 : (ts.tune ? Red : White);
	if (ts.keyer_mode.active)
	{
//		if (ts.buffered_tx)
//		{
//			cap = "TXRX B";
//		}
//		else
//		{
//			cap = "TXRX U";
//		}
		switch(ts.keyer_mode_tx)
		{
		case TXRX_U:
			cap = "TXRX U";
			break;
		case TXRX_B:
			cap = "TXRX B";
			break;
		case CYCLED:
			cap = "CYCLED";
			break;
		case BEACON:
			cap = "BEACON";
			break;
		}
		color = (ts.keyer_mode_tx == BEACON && ts.keyer_cycled_tx_active)? Red:  Blue2;
	}
	else if(ts.ExtFMenuActive)
	{
#ifdef SDR_AMBER
		cap = "RXIN+";
		color = White;
#else
		cap = "REST";
		color = White;
#endif
	}
    else if(ts.vfo_mem_flag)
    {
//		cap = "M4";
		color = White;
		if(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON)
		{
			cap = "-";
		}
		else
		{
			cap = "M4";
		}
    }
    else if(ts.XvtrFMenuActive)
    {
		cap = "XVTR 4";
		color = White;
    }
    else if(ts.MenuOfFMenusActive)
    {
        cap = "MAIN";
        color = Green;
    }
	else
	{
		cap = "TUNE";
	}
	UiDriver_DrawFButtonLabel(5, cap, color);
}

static inline void UiDriver_FButton_F6Illum()
{
#ifdef	SDR_AMBER_480_320
	const char* cap;
	uint32_t color = White;
	cap = "ILLUM";
	#ifdef SDR_AMBER_4INCH
	if(ts.expflags2 & EXPFLAGS2_AMBER_4INCH_F6_IND)
	{
		cap = "AT";
	}
	else
	{
		cap = "ILLUM";
	}
	#endif
	UiDriver_DrawFButtonLabel(6, cap, color);
#endif
}

static inline void UiDriver_FButton_F7AT()
{
#ifdef SDR_AMBER_4INCH
	const char* cap;
	uint32_t color = White;
if(ts.expflags2 & EXPFLAGS2_AMBER_4INCH_F7_IND)
{
	cap = "POWER";
}
else
{
	cap = "AT";
}
UiDriver_DrawFButtonLabel(7, cap, color);
#endif
}

void UiDriver_EncoderDisplaySimple(const uint8_t column, const uint8_t row, const char *label, bool encoder_active,
		uint32_t value)
{

	char temp[5];
	uint32_t color = encoder_active?White:Grey;

	snprintf(temp,5," %2lu",value);
	UiDriver_EncoderDisplay(column, row, label, encoder_active,
			temp, color);
}

void UiDriver_DisplaySplitFreqLabels()
{
	// in SPLIT mode?
	const char *split_rx, *split_tx;
	if (!(is_vfo_b()))
	{
		split_rx = "(A) RX->";  // Place identifying marker for RX frequency
		split_tx = "(B) TX->";  // Place identifying marker for TX frequency
#if !defined(SDR_AMBER_480_320) && !defined(OVI40_MOD_480_320)
		if(ts.show_wide_spectrum)
		{
			split_rx = "(A)RX->";
			split_tx = "(B)TX->";
		}
#else
		split_rx = "(A)RX->";
		split_tx = "(B)TX->";
#endif
	}
	else
	{
		split_rx = "(B) RX->";  // Place identifying marker for RX frequency
		split_tx = "(A) TX->";  // Place identifying marker for TX frequency
#if !defined(SDR_AMBER_480_320) && !defined(OVI40_MOD_480_320)
		if(ts.show_wide_spectrum)
		{
			split_rx = "(B)RX->";
			split_tx = "(A)TX->";
		}
#else
		split_rx = "(B)RX->";
		split_tx = "(A)TX->";
#endif
	}
	UiLcdHy28_PrintText(ts.Layout->TUNE_SPLIT_MARKER_X - (SMALL_FONT_WIDTH * 5),
			ts.Layout->TUNE_FREQ.y, split_rx, RX_Grey, Black,
			0);  // Place identifying marker for RX frequency
	UiLcdHy28_PrintText(ts.Layout->TUNE_SPLIT_MARKER_X - (SMALL_FONT_WIDTH * 5),
			ts.Layout->TUNE_SPLIT_FREQ_Y_TX, split_tx, TX_Grey, Black,
			0);  // Place identifying marker for TX frequency
}

void UiAction_CopyVfoAB()
{
//	// not in menu mode:  Make VFO A = VFO B or VFO B = VFO A, as appropriate
//	VfoReg* vfo_store;
//	if(is_vfo_b())      // are we in VFO B mode?
//	{
//		vfo_store = &vfo[VFO_A].band[ts.band->band_mode];
//	}
//	else        // we were in VFO A mode
//	{
//		vfo_store = &vfo[VFO_B].band[ts.band->band_mode];
//	}
    // Make VFO A = VFO B or VFO B = VFO A, as appropriate
    VfoReg* vfo_store = &vfo[is_vfo_b()? VFO_A : VFO_B].band[ts.band->band_mode];

	vfo_store->dial_value = df.tune_new;
	vfo_store->decod_mode = ts.dmod_mode;                   // copy active VFO settings into other VFO
	vfo_store->digital_mode = ts.digital_mode;

	UiDriver_FrequencyUpdateLOandDisplay(true);

	if (ts.menu_mode == false)
	{
		UiSpectrum_Clear();          // clear display under spectrum scope
		UiLcdHy28_PrintText(80,160,is_vfo_b()?"VFO B -> VFO A":"VFO A -> VFO B",Cyan,Black,1);
		HAL_Delay(3000);
		UiSpectrum_Init();           // init spectrum scope
	}
}

void UiAction_ToggleVfoAB()
{

	uint32_t old_dmod_mode = ts.dmod_mode;

	/**
	 * @warning - The two functions below call the same function
	 * -> RadioManagement_SetDemodMode() at the end,
	 * so some reorganization should be done to better handle switching modes,
	 * in a more central way...
	 */
	RadioManagement_ToggleVfoAB();
	UiDriver_SetDemodMode( ts.dmod_mode );

	UiDriver_FButton_F4ActiveVFO();

	// do frequency/display update
	if(is_splitmode())      // in SPLIT mode?
	{
		UiDriver_DisplaySplitFreqLabels();
	}

	// Change decode mode if need to
	if(ts.dmod_mode != old_dmod_mode)
	{
		UiDriver_UpdateDisplayAfterParamChange();
	}
	else
	{
		UiDriver_FrequencyUpdateLOandDisplay(true);
	}
}

void UiDriver_SetSplitMode(bool mode_active)
{
	if(mode_active)      // are we in SPLIT mode?
	{
		ts.vfo_mem_mode |= VFO_MEM_MODE_SPLIT;      // yes - turn on MSB to activate SPLIT
	}
	else // are we NOT in SPLIT mode?
	{
		ts.vfo_mem_mode &= ~VFO_MEM_MODE_SPLIT; // yes - turn off MSB to turn off SPLIT
	}
	UiDriver_CreateMainFreqDisplay(false);      //
	UiDriver_FrequencyUpdateLOandDisplay(true);
}

/**
 * @brief: process hardcoded buttons click and hold
 */
void UiDriver_RefreshEncoderDisplay()
{
	UiDriver_DisplayEncoderOneMode();
	UiDriver_DisplayEncoderTwoMode();
	UiDriver_DisplayEncoderThreeMode();
}

/**
 * @brief This is THE function to call after changing operational parameters such as frequency or demod mode
 * It will make sure to update the display AND also tunes to a newly selected frequency if not already tuned to it.
 */
bool UiDriver_IsDemodModeChange()
{
	bool retval = (ts.dmod_mode != ui_driver_state.dmod_mode);
	retval |= ts.dmod_mode == DEMOD_DIGI && ts.digital_mode != ui_driver_state.digital_mode;

	return retval;
}

/**
 * @brief cleans out mode specific ui elements before switching to next mode
 */
void UiDriver_ModeSpecificDisplayClear(uint8_t dmod_mode, uint8_t digital_mode)
{
	switch(dmod_mode)
	{
	case DEMOD_CW:
		UiDriver_TextMsgClear();
		CwDecoder_WpmDisplayClearOrPrepare(false);
		UiSpectrum_InitCwSnapDisplay(false);
		break;
	case DEMOD_DIGI:
	{
		switch(digital_mode)
		{
#ifdef USE_FREEDV
		case DigitalMode_FreeDV:
			FreeDv_DisplayClear();
			break;
#endif
		case DigitalMode_RTTY:
		case DigitalMode_BPSK:
			UiDriver_TextMsgClear();
			UiSpectrum_InitCwSnapDisplay(false);
			break;
		default:
			break;
		}
	}
	break;
	case DEMOD_AM:
	case DEMOD_SAM:
		UiSpectrum_InitCwSnapDisplay(false);
	break;

	default:
		break;
	}
}

/**
 * @brief prepares mode specific ui elements run the mode
 */
void UiDriver_ModeSpecificDisplayPrepare(uint8_t dmod_mode, uint8_t digital_mode)
{
	switch(dmod_mode)
	{
	case DEMOD_CW:
	    if (ts.cw_decoder_enable == true)
	    {
	        CwDecoder_WpmDisplayClearOrPrepare(true);
	    }
	    if(cw_decoder_config.snap_enable == true && ts.dmod_mode == DEMOD_CW)
	    {
	    	UiSpectrum_InitCwSnapDisplay(true);
	    }
		break;
	case DEMOD_DIGI:
	{
		switch(digital_mode)
		{
#ifdef USE_FREEDV
		case DigitalMode_FreeDV:
			FreeDv_DisplayPrepare();
			break;
#endif
		case DigitalMode_RTTY:
		case DigitalMode_BPSK:
			UiDriver_TextMsgClear();
		    if(cw_decoder_config.snap_enable == true)
		    {
		    	UiSpectrum_InitCwSnapDisplay(true);
		    }
			break;
		default:
			break;
		}
	}
	break;
	case DEMOD_AM:
	case DEMOD_SAM:
	    if(cw_decoder_config.snap_enable == true)
	    {
	    	UiSpectrum_InitCwSnapDisplay(true);
	    }
	break;

	default:
		break;
	}
}

void UiDriver_UpdateDemodSpecificDisplayAfterParamChange()
{
    // clear display content specific for the old mode
    UiDriver_ModeSpecificDisplayClear(ui_driver_state.dmod_mode,ui_driver_state.digital_mode);
    // prepare mode specific UI elements used in the new mode
    UiDriver_ModeSpecificDisplayPrepare(ts.dmod_mode,ts.digital_mode);
    ui_driver_state.dmod_mode = ts.dmod_mode;
    ui_driver_state.digital_mode = ts.digital_mode;
}

void UiDriver_UpdateDisplayAfterParamChange()
{
    // TODO Maybe we should split this, so that we clear BEFORE doing the general stuff
    // and prepare after, but for now it should work this way

    UiMenu_MapColors(ts.box_colour,NULL, &sd.boxes_colour);
    UiMenu_MapColors(ts.txtline_colour,NULL, &sd.txtline_colour);

	if (UiDriver_IsDemodModeChange())
	{
	    UiDriver_UpdateDemodSpecificDisplayAfterParamChange();
	}

	// below are the always present UI elements
	UiDriver_FrequencyUpdateLOandDisplay(false);   // update frequency display without checking encoder

	UiDriver_DisplayDemodMode();

	UiDriver_DisplayMemoryLabel();

	UiDriver_DisplayFilter();    // make certain that numerical on-screen bandwidth indicator is updated

	UiSpectrum_DisplayFilterBW();  // update on-screen filter bandwidth indicator (graphical)

	UiDriver_RefreshEncoderDisplay();

	if(ts.menu_mode)    // are we in menu mode?
	{
		UiMenu_RenderMenu(MENU_RENDER_ONLY);    // yes, update display when we change modes
	}

	UiVk_Redraw();			//virtual keypads call (refresh purpose)
#ifdef SDR_AMBER
  #ifndef SDR_AMBER_480_320
	UiLcdHy28_PrintTextVertLen(308, 43, "Amber", 5, Grey4, Black);
  #else
	UiLcdHy28_PrintTextVertLen(468, 47, "Amber", 5, Grey4, Black);
  #endif
#endif
    UiDriver_DisplayPowerLevel();
    UiDriver_DisplayFreqStepSize();
}
//
//
//*----------------------------------------------------------------------------
//* Function Name       : UiDriverPressHoldStep
//* Object              : Select the step size for the press-and-hold of the step size button
//* Input Parameters    : 0=Decrease step size, 1=Increase step size
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_PressHoldStep(uchar is_up)
{
	ulong	minus_idx, plus_idx;

	switch(df.selected_idx)	 		// select appropriate "alternate" step size based on current step size
	{
	case T_STEP_1HZ_IDX:	// 1Hz step size
	case T_STEP_5HZ_IDX:	// 10Hz step size
		minus_idx = T_STEP_1HZ_IDX;		// use 1 Hz as small step size
		plus_idx = T_STEP_1KHZ_IDX;		// use 1 kHz as large step size
		break;
	case T_STEP_10HZ_IDX:	// 10Hz step size
		minus_idx = T_STEP_1HZ_IDX;		// use 1 Hz as small step size
		plus_idx = T_STEP_1KHZ_IDX;		// use 1 kHz as large step size
		break;
	case T_STEP_50HZ_IDX:	// 10Hz step size
		minus_idx = T_STEP_1HZ_IDX;		// use 1 Hz as small step size
		plus_idx = T_STEP_1KHZ_IDX;		// use 1 kHz as large step size
		break;
	case T_STEP_100HZ_IDX:	// 100Hz step size
		minus_idx = T_STEP_10HZ_IDX;		// use 10 Hz as small step size
		plus_idx = T_STEP_1KHZ_IDX;		// use 1 kHz as large step size
		break;
	case T_STEP_10KHZ_IDX:	// 10 kHz step size
	case T_STEP_100KHZ_IDX:	// 100 kHz step size
		minus_idx = T_STEP_100HZ_IDX;	// use 100 Hz as small step size
		plus_idx = T_STEP_100KHZ_IDX;	// use 100 kHz as large step size
		break;
	case T_STEP_1KHZ_IDX:	// 1 kHz step size
	default:
		minus_idx = T_STEP_10HZ_IDX;	// use 10 Hz as small step size
		plus_idx = T_STEP_10KHZ_IDX;	// use 10 kHz as large step size
		break;
	}

	if(!is_up)	 		// temporary decrease of step size
	{
		ts.tune_step = STEP_PRESS_MINUS;
		ts.tune_step_idx_holder = df.selected_idx;
		if(df.selected_idx)
			df.tuning_step	= tune_steps[minus_idx];
		df.selected_idx = minus_idx;
	}
	else	 			// temporary increase of step size
	{
		ts.tune_step = STEP_PRESS_PLUS;
		ts.tune_step_idx_holder = df.selected_idx;
		df.tuning_step	= tune_steps[plus_idx];
		df.selected_idx = plus_idx;
	}
	//
	UiDriver_DisplayFreqStepSize();		// update display
}

void UiDriver_DisplayDemodMode()
{
	// Clear control
	char* txt = "???";
//	uint16_t clr_fg = White,clr_bg = Blue;
	uint16_t clr_fg = White,clr_bg = sd.boxes_colour;

	// Create Decode Mode ind. (USB/LSB/AM/FM/CW)
#if !defined(SDR_AMBER_480_320) && !defined(OVI40_MOD_480_320)
	if(!ts.show_wide_spectrum)
	{
		switch(ts.dmod_mode)
		{
		case DEMOD_USB:
			txt = "USB";
			break;
		case DEMOD_LSB:
			txt = "LSB";
			break;
		case DEMOD_SAM:
			if(ads.sam_sideband == SAM_SIDEBAND_LSB)
			{
				txt = "SAM-L";
			}
			else if (ads.sam_sideband == SAM_SIDEBAND_USB)
			{
				txt = "SAM-U";
			}
	#ifdef USE_TWO_CHANNEL_AUDIO
			else if (ads.sam_sideband == SAM_SIDEBAND_STEREO)
			{
				txt = "SAM-S";
			}
	#endif
			else
			{
				txt = "SAM";
			}
			break;
		case DEMOD_AM:
			txt = "AM";
			break;
		case DEMOD_FM:
			txt = RadioManagement_FmDevIs5khz() ? "FM-W" : "FM-N";
			{
				if(ts.txrx_mode == TRX_MODE_RX)
				{
					if(ads.fm_conf.squelched == false)
					{
						// is audio not squelched?
						if((ads.fm_conf.subaudible_tone_detected) && (ts.fm_subaudible_tone_det_select))
						{
							// is tone decoding enabled AND a tone being detected?
							clr_fg =  Black;
							clr_bg = Red2;	// Not squelched, passing audio - change color!
						}
						else  	// tone decoder disabled - squelch only
						{
							clr_fg = Black;
							clr_bg = White;	// Not squelched, passing audio - change color, but different from tone
						}
					}
				}
				else if(ts.txrx_mode == TRX_MODE_TX)	 	// in transmit mode?
				{
					if(ads.fm_conf.tone_burst_active)	 		// yes - is tone burst active?
					{
						clr_fg = Black;
						clr_bg = Yellow;	// Yes, make "FM" yellow
					}
				}
				break;
			}
		case DEMOD_CW:
			txt = ts.cw_lsb?"CW-L":"CW-U";
			break;
		case DEMOD_DIGI:
			switch(ts.digital_mode)
			{
			case DigitalMode_RTTY:
				txt = ts.digi_lsb?"RT-L":"RT-U";
				break;
			case DigitalMode_BPSK:
				txt = ts.digi_lsb?"PSK-L":"PSK-U";
				break;
			default:
				txt = ts.digi_lsb?"DI-L":"DI-U";
			}
			break;
	#ifdef USE_TWO_CHANNEL_AUDIO
		case DEMOD_IQ:
			txt = "IQ-S";
			break;
		case DEMOD_SSBSTEREO:
			txt = "SSB-S";
			break;
	#endif
			default:
				break;
		}
	}
	else// show_wide_spectrum
	{
#endif
		switch(ts.dmod_mode)
		{
		case DEMOD_USB:
//			txt = "U";
			txt = " ";
			break;
		case DEMOD_LSB:
//			txt = "L";
			txt = " ";
			break;
		case DEMOD_SAM:
			if(ads.sam_sideband == SAM_SIDEBAND_LSB)
			{
				txt = "L";//"SAM-L";
			}
			else if (ads.sam_sideband == SAM_SIDEBAND_USB)
			{
				txt = "U";//"SAM-U";
			}
#ifdef USE_TWO_CHANNEL_AUDIO
			else if (ads.sam_sideband == SAM_SIDEBAND_STEREO)
			{
				txt = "S";//"SAM-S";
			}
#endif
			else
			{
				txt = " ";//"SAM";
			}
			break;
		case DEMOD_AM:
			txt = " ";//"AM";
			break;
		case DEMOD_FM:
			txt = RadioManagement_FmDevIs5khz() ? "W" : "N"; //"FM-..."
			{
				if(ts.txrx_mode == TRX_MODE_RX)
				{
					if(ads.fm_conf.squelched == false)
					{
						// is audio not squelched?
						if((ads.fm_conf.subaudible_tone_detected) && (ts.fm_subaudible_tone_det_select))
						{
							// is tone decoding enabled AND a tone being detected?
							clr_fg =  Black;
							clr_bg = Red2;  // Not squelched, passing audio - change color!
						}
						else    // tone decoder disabled - squelch only
						{
							clr_fg = Black;
							clr_bg = White; // Not squelched, passing audio - change color, but different from tone
						}
					}
				}
				else if(ts.txrx_mode == TRX_MODE_TX)        // in transmit mode?
				{
					if(ads.fm_conf.tone_burst_active)           // yes - is tone burst active?
					{
						clr_fg = Black;
						clr_bg = Yellow;    // Yes, make "FM" yellow
					}
				}
				break;
			}
		case DEMOD_CW:
			txt = ts.cw_lsb?"L":"U";
			break;
		case DEMOD_DIGI:
			switch(ts.digital_mode)
			{
			case DigitalMode_RTTY:
				txt = ts.digi_lsb?"L":"U";
				break;
			case DigitalMode_BPSK:
				txt = ts.digi_lsb?"L":"U";
				break;
			default:
				txt = ts.digi_lsb?"L":"U";
			}
			break;
#ifdef USE_TWO_CHANNEL_AUDIO
		case DEMOD_IQ:
			txt = "S";
			break;
		case DEMOD_SSBSTEREO:
			txt = "S";
			break;
#endif
			default:
				break;
		}
#if !defined(SDR_AMBER_480_320) && !defined(OVI40_MOD_480_320)
	}
#endif
	UiLcdHy28_PrintTextCentered(ts.Layout->DEMOD_MODE_MASK.x,ts.Layout->DEMOD_MODE_MASK.y,ts.Layout->DEMOD_MODE_MASK.w,txt,clr_fg,clr_bg,0);

	UiDriver_DisplayModulationType();
}

/**
 * This function gives a visual indication of the selected step size for the tuning knob. It draws a line under the respective digit in the frequency.
 * This function is closely coupled to the code for displaying the frequency digits.
 */
void UiDriver_DisplayFreqStepSize()
{

	static	bool	step_line = 0;	// used to indicate the presence of a step line

	const uint16_t font_width = is_splitmode()?SMALL_FONT_WIDTH:LARGE_FONT_WIDTH;
    const uint16_t x_pos = is_splitmode()?ts.Layout->TUNE_SPLIT_FREQ_X:ts.Layout->TUNE_FREQ.x;
    const uint16_t x_right = x_pos + (9* font_width);
	const uint32_t color = ts.tune_step?Cyan:White;		// is this a "Temporary" step size from press-and-hold?
//	const uint32_t stepsize_background = (ts.flags1 & FLAGS1_DYN_TUNE_ENABLE)?Blue:Black;
	const uint32_t stepsize_background = (ts.flags1 & FLAGS1_DYN_TUNE_ENABLE)?sd.boxes_colour:Black;
	// dynamic_tuning active -> yes, display on Grey3

	if(step_line)	 	// Remove underline indicating step size if one had been drawn
	{
        const int32_t space_l = 3*(LARGE_FONT_WIDTH * 3 + LARGE_FONT_WIDTH/2); //3 digits plus a half width dot
        const int32_t space_s = 3*(SMALL_FONT_WIDTH * 3 + SMALL_FONT_WIDTH/2); //3 digits plus a half width dot

		UiLcdHy28_DrawStraightLineDouble(ts.Layout->TUNE_FREQ.x,(ts.Layout->TUNE_FREQ.y + 24),space_l,LCD_DIR_HORIZONTAL,Black);
		UiLcdHy28_DrawStraightLineDouble(ts.Layout->TUNE_SPLIT_FREQ_X,(ts.Layout->TUNE_FREQ.y + 24),space_s,LCD_DIR_HORIZONTAL,Black);
	}

	// Blank old step size
	// UiLcdHy28_DrawFullRect(POS_TUNE_STEP_X,POS_TUNE_STEP_Y-1,POS_TUNE_STEP_MASK_H,POS_TUNE_STEP_MASK_W,stepsize_background);

	char step_name[10];
	// I know the code below will not win the price for the most readable code
	// ever. But it does the job of display any freq step somewhat reasonable.
	// khz/Mhz only whole  khz/Mhz is shown, no fraction
	// showing fractions would require some more coding, which is not yet necessary
	const int32_t pow10 = log10f(df.tuning_step);
	const int32_t digit_group = pow10/3;
	const int32_t digit_idx = pow10%3;

//	const char* stepUnitPrefix[] = { "","k","M","G","T"};
//	snprintf(step_name,10,"%d%sHz",(int)(df.tuning_step/exp10((digit_group)*3)), stepUnitPrefix[digit_group]);
	const char* stepUnitPrefix[] = { "Hz","k","M","G","T"};
	snprintf(step_name,10,"%d%s",(int)(df.tuning_step/exp10((digit_group)*3)), stepUnitPrefix[digit_group]);

	UiLcdHy28_PrintTextCentered(ts.Layout->TUNE_STEP.x,ts.Layout->TUNE_STEP.y,ts.Layout->TUNE_STEP.w,step_name,color,stepsize_background,0);

	if((ts.freq_step_config & FREQ_STEP_SHOW_MARKER) && pow10 < MAX_DIGITS)          // is frequency step marker line enabled?
	{

	    const int32_t group_space = (font_width * 3) + font_width/2; //3 digits plus a half width dot

	    const uint32_t line_pos =  x_right -  digit_idx * font_width - (digit_group * group_space);

	    UiLcdHy28_DrawStraightLineDouble(line_pos, (ts.Layout->TUNE_FREQ.y + 24),font_width,LCD_DIR_HORIZONTAL,White);
	    step_line = 1;	// indicate that a line under the step size had been drawn
	}
	else	// marker line not enabled
	{
	    step_line = 0;	// we don't need to erase "step size" marker line in the future
	}
}


typedef struct
{
	uint32_t start;
	uint32_t end;
	const char* name;
} BandGenInfo;

const BandGenInfo bandGenInfo[] =
{
		{150000, 285000, "LW" },
		{525000, 1605000, "MW" },
		{2300000, 2495000, "120m" },
		{3200000, 3400000, "90m" },
		{3900000, 4000000, "75m" },
		{4750000, 5060000, "60m" },
		{5950000, 6200000, "49m" },
		{7300000, 7350000, "41m" },
		{9400000, 9900000, "31m" },
		{11600000, 12100000, "25m" },
		{13570000, 13870000, "22m" },
		{15100000, 15800000, "19m" },
		{17480000, 17900000, "16m" },
		{18900000, 19020000, "15m" },
		{21450000, 21750000, "13m" },
//		{25670000, 26100000, "11m" },
        {25670000, 26959999, "11m" },
//		{26965000, 27405000, "11m" },
//      {26960000, 27860000, "11m" },
        {26960000, 27991250, "11m" },
		{ 0,  0,             "Gen" }
};
//*----------------------------------------------------------------------------
void UiDriver_ClearMemoryLabel()
{
#ifdef SDR_AMBER
#ifdef USE_FREEDV
        UiLcdHy28_PrintText(ts.Layout->MEMORYLABEL.x,  ts.Layout->MEMORYLABEL.y,"      ",Black,Black,0);
#else
        UiLcdHy28_PrintText(ts.Layout->MEMORYLABEL.x,  ts.Layout->MEMORYLABEL.y,"        ",Black,Black,0);
#endif
#endif
}
//*----------------------------------------------------------------------------
//static void UiDriver_DisplayMemoryLabel()
void UiDriver_DisplayMemoryLabel()
{
    if(ts.expflags1 & EXPFLAGS1_NO_SHOW_BNDMEM)
    {
#ifndef SDR_AMBER
#ifdef USE_FREEDV
        UiLcdHy28_PrintText(ts.Layout->MEMORYLABEL.x,  ts.Layout->MEMORYLABEL.y,"      ",Black,Black,0);
#else
        UiLcdHy28_PrintText(ts.Layout->MEMORYLABEL.x,  ts.Layout->MEMORYLABEL.y,"        ",Black,Black,0);
#endif
#endif
        return;
    }
	char txt[12];
//	uint32_t col = White;
#ifdef SDR_AMBER
	uint32_t col = (!ts.show_wide_spectrum?White:Blue);
#else
	uint32_t col = White;
#endif
	if (ts.band->band_mode < MAX_BAND_NUM && ts.cat_band_index == 255)
	{

#ifdef USE_MEMORY_MODE
	    // Enable all band memories, don't use band names
        snprintf(txt,12,"M%02d  ", ts.band->band_mode);
#else
        // Each memory has its designated band, use that as band
  #ifndef SDR_AMBER
  	  #ifndef OVI40_MOD_480_320
        snprintf(txt,12,"Bnd%s   ", ts.band->name);
  	  #else
        snprintf(txt,12,"B%s   ", ts.band->name);
  	  #endif
  #else
        snprintf(txt,12,"B%s   ", ts.band->name);
  #endif
#endif
	}
	if (ts.cat_band_index != 255)		// no band storage place active because of "CAT running in sandbox"
	{
		snprintf(txt,12," CAT  ");
	}
#ifndef SDR_AMBER
	txt[8]='\0'; // "Bnd" + 4digits + "m" = max 8 characters. We make sure the string is never longer than that.
#else
	txt[6]='\0'; // "B" + 4digits + "m" = max 6 characters.
#endif
//#ifdef USE_FREEDV
//        UiLcdHy28_PrintText(ts.Layout->MEMORYLABEL.x,  ts.Layout->MEMORYLABEL.y,"      ",col,Black,0);
//#else
//        UiLcdHy28_PrintText(ts.Layout->MEMORYLABEL.x,  ts.Layout->MEMORYLABEL.y,"        ",col,Black,0);
//#endif
	UiLcdHy28_PrintText(ts.Layout->MEMORYLABEL.x,  ts.Layout->MEMORYLABEL.y,txt,col,Black,0);
}

/**
 * Display the ham or broadcast band name of the currently selected band
 * @param band the band id, last band id is BAND_MODE_GEN, which is everything outside ham bands
 */
static void UiDriver_DisplayBand(const BandInfo* band)
{

	if (band != NULL)
	{
	    const char* bandName;
	    bool print_bc_name = true;

		uint16_t col = Orange; // default color for non-bc band

		// only if we are not in a ham band, we check the name of a broadcast band
		if (RadioManagement_IsGenericBand(band))
		{
		    int idx;
			for (idx = 0; bandGenInfo[idx].start !=0; idx++)
			{
				if (df.tune_old >= bandGenInfo[idx].start && df.tune_old < bandGenInfo[idx].end)
				{
					break; // found match
				}
			}

			if (bandGenInfo[idx].start !=0)
			{
				// Print name of BC band in yellow, if frequency is within a broadcast band
				col = Yellow;
			}

			if  (bandGenInfo[idx].start == 26965000)
			{
				col = Blue;		// CB radio == blue
			}

			if (idx == ts.bc_band)
			{
				print_bc_name = false;
			}

			ts.bc_band =idx;

			bandName = bandGenInfo[idx].name;
		}
		else
		{
			print_bc_name = true;
			bandName = band->name;
			ts.bc_band = 0xff;
		}
		if (print_bc_name)
		{
			UiLcdHy28_DrawFullRect(ts.Layout->BAND_MODE_MASK.x,ts.Layout->BAND_MODE_MASK.y,ts.Layout->BAND_MODE_MASK.h,ts.Layout->BAND_MODE_MASK.w,Black);
			UiLcdHy28_PrintTextRight(ts.Layout->BAND_MODE.x + 5*8,ts.Layout->BAND_MODE.y,bandName,col,Black,0);
		}
	}
}

//*----------------------------------------------------------------------------
//* Function Name       : UiDriverInitMainFreqDisplay
//* Object              :
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
//static void UiDriver_CreateMainFreqDisplay()
void UiDriver_CreateMainFreqDisplay(bool all_digits)
{
//	UiDriver_FButton_F3MemSplit();
    // TODO: Adjust ts.Layout->TUNE_FREQ.x to match 10 digits display approach, would simplify code
    const uint16_t font_width = LARGE_FONT_WIDTH;
    const uint16_t x_right = ts.Layout->TUNE_FREQ.x + (9* font_width);
    const int32_t group_space = (font_width * 3) + font_width/2; //3 digits plus a half width dot
    uint32_t box_width = 0;
//    const uint32_t box_width =  x_right -  1 * font_width - (3 * group_space); // 3 x 3 digits in a group with a dot + 1 x single digit == 10 digits
//    const uint32_t box_width =  font_width + (3 * group_space); // 3 x 3 digits in a group with a dot + 1 x single digit == 10 digits
    if(!(ts.xverter_mode & 0xf) && !all_digits) // XVTR is OFF & not forse clear all digits
    {
    	box_width = (2 * font_width) + (2 * group_space); // == 8 digits
    }
    else
    {
    	box_width =  font_width + (3 * group_space); // 3 x 3 digits in a group with a dot + 1 x single digit == 10 digits
    }
//    if(!ts.show_wide_spectrum)
//    {
    	UiLcdHy28_DrawFullRect(x_right - box_width,ts.Layout->TUNE_FREQ.y,24, box_width, Black);
//    }
//    else
//    {
//    	UiLcdHy28_DrawFullRect(120,ts.Layout->TUNE_FREQ.y,24, 157, Black);
//    }

    // clear frequency display area for large digits, which is also the max area for split

    UiDriver_FButton_F3MemSplit();
	if((is_splitmode()))	 	// are we in SPLIT mode?
	{
//		UiLcdHy28_PrintText(ts.Layout->TUNE_FREQ.x-16,ts.Layout->TUNE_FREQ.y,"          ",White,Black,1);	// clear large frequency digits
		UiDriver_DisplaySplitFreqLabels();
	}
	UiDriver_DisplayFreqStepSize();
}

//*----------------------------------------------------------------------------
//* Function Name       : UiDriverCreateFunctionButtons
//* Object              : function keys based on decoder mode
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
void UiDriver_CreateFunctionButtons(bool full_repaint)
{
	// Create bottom bar
	if(full_repaint)
	{
#ifndef SDR_AMBER_480_320
		for (int i = 0; i < 5; i++)
#else
    #ifdef SDR_AMBER_4INCH
		for (int i = 0; i < 7; i++)
    #else
		for (int i = 0; i < 6; i++)
    #endif
#endif
		{
			UiLcdHy28_DrawBottomButton((ts.Layout->BOTTOM_BAR.x + (ts.Layout->BOTTOM_BAR.w+1)*i),(ts.Layout->BOTTOM_BAR.y - 4),ts.Layout->BOTTOM_BAR.h,ts.Layout->BOTTOM_BAR.w,Grey);
		}
	}

	// Button F1
	UiDriver_DisplayFButton_F1MenuExit();
	// Button F2
	UiDriver_DisplayFButton_F2SnapMeter();
	// Button F3
	UiDriver_FButton_F3MemSplit();
	// Button F4
	UiDriver_FButton_F4ActiveVFO();
	// Button F5
	UiDriver_FButton_F5Tune();
#ifdef SDR_AMBER_480_320
	//Button F6 (480*320)
	UiDriver_FButton_F6Illum();
#endif
#ifdef SDR_AMBER_4INCH
	//Button F7 (480*320 4 inch)
	UiDriver_FButton_F7AT();
#endif
}

void UiDriver_SetSpectrumMode(SpectrumMode_t mode)
{
    ts.flags1 = (ts.flags1 & ~(FLAGS1_SCOPE_ENABLED | FLAGS1_WFALL_ENABLED)) |(mode << 7);
}
SpectrumMode_t UiDriver_GetSpectrumMode()
{
    return (ts.flags1 & (FLAGS1_SCOPE_ENABLED | FLAGS1_WFALL_ENABLED))  >> 7;
}

//
//*----------------------------------------------------------------------------
//* Function Name       : UiDriverDrawSMeter
//* Object              : draw the part of the S meter
//* Input Parameters    : uchar color
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_CreateDesktop(void)
{
	// Backlight off - hide startup logo
	UiLcdHy28_BacklightEnable(false);

	// Clear display
	UiLcdHy28_LcdClear(Black);

	// Frequency
	UiDriver_CreateMainFreqDisplay(false);

	// Function buttons
	UiDriver_CreateFunctionButtons(true);

	// S-meter
	UiDriver_CreateMeters();

	if (UiDriver_GetSpectrumMode() == SPECTRUM_BLANK)
	{
	    UiDriver_SetSpectrumMode(SPECTRUM_DUAL);
	}

	//UiSpectrum_GetSpectrumGraticule()->y=ts.graticulePowerupYpos;
	// Spectrum scope
	UiSpectrum_Init();
	//*(volatile uint32_t*)(0x20000000 + 0x0002FFFC) = 123;


	UiDriver_RefreshEncoderDisplay();

	// Power level
	UiDriver_DisplayPowerLevel();


	UiDriver_CreateVoltageDisplay();

	UiDriver_CreateTemperatureDisplay();

	// Set correct frequency
	UiDriver_FrequencyUpdateLOandDisplay(true);

	ts.dsp.active =  (ts.dsp.active & ~(DSP_NR_ENABLE|DSP_ANR_ENABLE|DSP_NOTCH_ENABLE|DSP_MNOTCH_ENABLE|DSP_MPEAK_ENABLE)); // reset DSP state

	UiDriver_UpdateDisplayAfterParamChange();

	// Backlight on - only when all is drawn
	UiLcdHy28_BacklightEnable(true);

#ifdef SDR_AMBER
  #ifndef SDR_AMBER_480_320
  UiLcdHy28_PrintTextVertLen(308, 43, "Amber", 5, Grey4, Black);
  #else
  UiLcdHy28_PrintTextVertLen(468, 47, "Amber", 5, Grey4, Black);
  #endif
	if(ts.expflags1 & EXPFLAGS1_SHOW_GREETING)
	{
	UiDriver_ShowGreeting(0);
	ts.expflags1 = ts.expflags1 & (~EXPFLAGS1_SHOW_GREETING); // show just one time
	}
    Board_AmberIOx8_Write(255);  // set all ports
    Board_IllumButt();
    Board_AmberIOx4_Write(3, 0); // configurate all ports as outputs
    ts.amber_io4_state = 0;
    Board_AmberIOx4_Write(1, 0); // reset all ports
#endif

    if(ts.expflags2 & EXPFLAGS2_DISABLE_TP)
    {
        ts.disabled_tp = true;
    }

#ifndef UI_BRD_MCHF
    MIC_bias_set();
#endif

	CW_Smooth_Settings();
//	UiDriver_ToggleWideSpectrumOnStart();
}


static void UiDriver_DrawSMeter(uint16_t color)
{
	// Draw top line
//	UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x +  18),(ts.Layout->SM_IND.y + 20),92,LCD_DIR_HORIZONTAL,color);
	UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x +  18),(ts.Layout->SM_IND.y + 20 - (!ts.show_wide_spectrum?0:2)),92,LCD_DIR_HORIZONTAL,color);
	// Draw s markers on top white line
	for(uint16_t i = 0; i < 10; i++)
	{
		uint8_t 	v_s;
		// Draw s text, only odd numbers
		if(i%2)
		{
			v_s = 5;
		}
		else
		{
			v_s = 3;
		}
		// Lines
//		UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 20) - v_s),v_s,LCD_DIR_VERTICAL,color);
        UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 20 - (!ts.show_wide_spectrum?0:2)) - v_s),v_s,LCD_DIR_VERTICAL,color);
	}
}
//



#define BTM_MINUS 14

//*----------------------------------------------------------------------------
//* Function Name       : UiDriverDeleteSMeter
//* Object              : delete the S meter
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_DeleteMeters()
{
	UiLcdHy28_DrawFullRect(ts.Layout->SM_IND.x+1,ts.Layout->SM_IND.y+1,ts.Layout->SM_IND.h ,ts.Layout->SM_IND.w,Black);
}

static void UiDriver_DeleteSMeterLabels(void)
{
	UiLcdHy28_DrawFullRect(ts.Layout->SM_IND.x+1,ts.Layout->SM_IND.y+1,21,ts.Layout->SM_IND.w,Black);
#ifdef SDR_AMBER
  #ifndef SDR_AMBER_480_320
  UiLcdHy28_PrintTextVertLen(308, 43, "Amber", 5, Grey4, Black);
  #else
  UiLcdHy28_PrintTextVertLen(468, 47, "Amber", 5, Grey4, Black);
  #endif
#endif
}


static void UiDriver_DrawPowerMeterLabels(void)
{

//  const uint16_t y_pos = (ts.Layout->SM_IND.y + 5);
    const uint16_t y_pos = (ts.Layout->SM_IND.y + 5 - (!ts.show_wide_spectrum?0:2));
    const uint16_t x_pos = (ts.Layout->SM_IND.x + 18);

    const int32_t maxW = ((mchf_pa.max_power > 5000) ? mchf_pa.max_power : 5000)  / 1000;

    // get the pwr increment in next integer number of 0.5W steps
    const float32_t PWR_INCR = (maxW % 5 == 0)? (maxW/10.0) : (maxW/5+1)/2.0  ; //.

	// Leading text

	UiLcdHy28_PrintText(x_pos - 12 , y_pos,"P" , White, Black, 4);
	UiLcdHy28_PrintText(x_pos + 167, y_pos," W", White, Black, 4);

	// Draw middle line
	UiLcdHy28_DrawStraightLineDouble(x_pos, y_pos + 15, 170, LCD_DIR_HORIZONTAL, White);

	// Draw s markers on middle white line
	for(int i = 0; i < 12; i++)
	{

		uint16_t pwr_val = i * PWR_INCR;

        char    num[4];

		if(pwr_val < 10)
		{
			num[0] = pwr_val + 0x30;
			num[1] = 0;
		}
		else if (pwr_val < 100)
		{
			num[0] = pwr_val/10 + 0x30;
			num[1] = pwr_val%10 + 0x30;
			num[2] = 0;
		}
        else if (pwr_val < 1000)
        {
            num[0] = pwr_val/100 + 0x30;
            num[1] = (pwr_val%100)/10 + 0x30;
            num[2] = pwr_val%10 + 0x30;
            num[3] = 0;
        }
		else
		{
		    num[0] = 0; // no value display for larger values
		}
		const int dw = UiLcdHy28_TextWidth(num,4);

		// Only every second value is displayed as number,
		// even indicies are used
		if(i%2 == 0)
		{
			UiLcdHy28_PrintText((x_pos - dw/2 + i*15), y_pos, num, White,Black,4);
		}

		// Lines, even indicies are shorter to make room for numbers
		uint8_t v_s= (i%2 != 0)? 3 : 5;

		UiLcdHy28_DrawStraightLineDouble((x_pos + i*15),(y_pos + 15) - v_s, v_s, LCD_DIR_VERTICAL, White);
	}


}
static void UiDriver_DrawSMeterLabels(void)
{
	uchar   i,v_s;
	char    num[20];

	// Draw top line
//	UiLcdHy28_DrawFullRect((ts.Layout->SM_IND.x +  18),(ts.Layout->SM_IND.y + 20),2,92,White);
//	UiLcdHy28_DrawFullRect((ts.Layout->SM_IND.x +  113),(ts.Layout->SM_IND.y + 20),2,75,Green);
	UiLcdHy28_DrawFullRect((ts.Layout->SM_IND.x +  18),(ts.Layout->SM_IND.y + 20 - (!ts.show_wide_spectrum?0:2)),2,92,White);
	UiLcdHy28_DrawFullRect((ts.Layout->SM_IND.x +  113),(ts.Layout->SM_IND.y + 20 - (!ts.show_wide_spectrum?0:2)),2,75,Green);

	// Leading text
//	UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 12),(ts.Layout->SM_IND.y +  5),"S",  White,Black,4);
	UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 12),(ts.Layout->SM_IND.y +  5 - (!ts.show_wide_spectrum?0:2)),"S",  White,Black,4);

	// Trailing text
//	UiLcdHy28_PrintText((ts.Layout->SM_IND.x + 185),(ts.Layout->SM_IND.y + 5), "dB",Green,Black,4);
	UiLcdHy28_PrintText((ts.Layout->SM_IND.x + 185),(ts.Layout->SM_IND.y + 5 - (!ts.show_wide_spectrum?0:2)), "dB",Green,Black,4);


	num[1] = 0;
	// Draw s markers on top white line
	for(i = 0; i < 10; i++)
	{
		num[0] = i + 0x30;

		// Draw s text, only odd numbers
		if(i%2)
		{
//			UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 4 + i*10),(ts.Layout->SM_IND.y + 5),num,White,Black,4);
			UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 4 + i*10),(ts.Layout->SM_IND.y + 5 - (!ts.show_wide_spectrum?0:2)),num,White,Black,4);
			v_s = 5;
		}
		else
			v_s = 3;

		// Lines
//		UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 20) - v_s),v_s,LCD_DIR_VERTICAL,White);
		UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 20 - (!ts.show_wide_spectrum?0:2)) - v_s),v_s,LCD_DIR_VERTICAL,White);
	}

	num[1] = 0x30;
	num[2] = 0x00;
	// Draw s markers on top green line
	for(i = 0; i < 4; i++)
	{
		// Prepare text
		num[0] = i*2 + 0x30;

		if(i)
		{
			// Draw text
//			UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 108) - 6 + i*20),(ts.Layout->SM_IND.y + 5),num,Green,Black,4);
			UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 108) - 6 + i*20),(ts.Layout->SM_IND.y + 5 - (!ts.show_wide_spectrum?0:2)),num,Green,Black,4);

			// Draw vert lines
//			UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 108) + i*20),(ts.Layout->SM_IND.y + 15),5,LCD_DIR_VERTICAL,Green);
			UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 108) + i*20),(ts.Layout->SM_IND.y + 15 - (!ts.show_wide_spectrum?0:2)),5,LCD_DIR_VERTICAL,Green);
		}
	}

}

static void UiDriver_CreateMeters()
{
	uchar 	i;
	char	num[20];
	int		col;

	UiLcdHy28_DrawEmptyRect(ts.Layout->SM_IND.x,ts.Layout->SM_IND.y,ts.Layout->SM_IND.h,ts.Layout->SM_IND.w + 2,Grey);

	if (ts.txrx_mode == TRX_MODE_RX)
	{
		UiDriver_DrawSMeterLabels();
	}
	else
	{
		UiDriver_DrawPowerMeterLabels();
	}

	if(ts.tx_meter_mode == METER_SWR)
	{
//		UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 12),(ts.Layout->SM_IND.y + 59 - BTM_MINUS),"SWR",Red2,Black,4);
		UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 12),(ts.Layout->SM_IND.y + 59 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)),"SWR",Red2,Black,4);

		// Draw bottom line for SWR indicator
//		UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 18),(ts.Layout->SM_IND.y + 55 - BTM_MINUS), 62,LCD_DIR_HORIZONTAL,White);
//		UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 83),(ts.Layout->SM_IND.y + 55 - BTM_MINUS),105,LCD_DIR_HORIZONTAL,Red);
        UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 18),(ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)), 62,LCD_DIR_HORIZONTAL,White);
        UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 83),(ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)),105,LCD_DIR_HORIZONTAL,Red);
		col = White;

		// Draw S markers on middle white line
		for(i = 0; i < 12; i++)
		{
			if(i > 6) col = Red;

			if(!(i%2))
			{
				if(i)
				{
					num[0] = i/2 + 0x30;
					num[1] = 0;

					// Text
//					UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 3 + i*10),(ts.Layout->SM_IND.y + 59 - BTM_MINUS),num,White,Black,4);
//
//					UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS) - 2),2,LCD_DIR_VERTICAL,col);
                    UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 3 + i*10),(ts.Layout->SM_IND.y + 59 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)),num,White,Black,4);
                    UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)) - 2),2,LCD_DIR_VERTICAL,col);
				}
				else
				{
//					UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS) - 7),7,LCD_DIR_VERTICAL,col);
					UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)) - 7),7,LCD_DIR_VERTICAL,col);
				}
			}
		}
	}
	else if(ts.tx_meter_mode == METER_ALC)
	{
//		UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 12),(ts.Layout->SM_IND.y + 59 - BTM_MINUS),"ALC",Yellow,Black,4);
		UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 12),(ts.Layout->SM_IND.y + 59 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)),"ALC",Yellow,Black,4);


//		UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 18),(ts.Layout->SM_IND.y + 55 - BTM_MINUS), 62,LCD_DIR_HORIZONTAL,White);
//		UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 83),(ts.Layout->SM_IND.y + 55 - BTM_MINUS),105,LCD_DIR_HORIZONTAL,Red);
        UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 18),(ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)), 62,LCD_DIR_HORIZONTAL,White);
        UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 83),(ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)),105,LCD_DIR_HORIZONTAL,Red);

		col = White;

		// Draw markers on middle line
		for(i = 0; i < 17; i++)
		{
			if(i > 6) col = Red;
			if(!(i%2))
			{
				if(i)
				{
					snprintf(num,20,"%d",(i*2));
					// Text
//					UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 3 + i*10),(ts.Layout->SM_IND.y + 59 - BTM_MINUS),num,White,Black,4);
//
//					UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS) - 2),2,LCD_DIR_VERTICAL,col);
                    UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 3 + i*10),(ts.Layout->SM_IND.y + 59 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)),num,White,Black,4);
                    UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS) - 2 - (!ts.show_wide_spectrum?0:3)),2,LCD_DIR_VERTICAL,col);
				}
				else
				{
//					UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS) - 7),7,LCD_DIR_VERTICAL,col);
                    UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)) - 7),7,LCD_DIR_VERTICAL,col);
				}
			}
		}
	}
	else if(ts.tx_meter_mode == METER_AUDIO)
	{
//		UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 12),(ts.Layout->SM_IND.y + 59 - BTM_MINUS),"AUD",Cyan,Black,4);
		UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 12),(ts.Layout->SM_IND.y + 59 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)),"AUD",Cyan,Black,4);

		// Draw bottom line for SWR indicator
//		UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 18),(ts.Layout->SM_IND.y + 55 - BTM_MINUS), 108,LCD_DIR_HORIZONTAL,White);
//		UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 129),(ts.Layout->SM_IND.y + 55 - BTM_MINUS),59,LCD_DIR_HORIZONTAL,Red);
        UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 18),(ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)), 108,LCD_DIR_HORIZONTAL,White);
        UiLcdHy28_DrawStraightLineDouble((ts.Layout->SM_IND.x + 129),(ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)),59,LCD_DIR_HORIZONTAL,Red);
		col = White;

		// Draw markers on middle line
		for(i = 0; i < 17; i++)
		{
			if(i > 10) col = Red;
			if(!(i%2))
			{
				if(i)
				{
					snprintf(num,20,"%d",(i*2)-20);
					// Text
//					UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 3 + i*10),(ts.Layout->SM_IND.y + 59 - BTM_MINUS),num,White,Black,4);
//
//					UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS) - 2),2,LCD_DIR_VERTICAL,col);
                    UiLcdHy28_PrintText(((ts.Layout->SM_IND.x + 18) - 3 + i*10),(ts.Layout->SM_IND.y + 59 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)),num,White,Black,4);
                    UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)) - 2),2,LCD_DIR_VERTICAL,col);
				}
				else
				{
//					UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS) - 7),7,LCD_DIR_VERTICAL,col);
                    UiLcdHy28_DrawStraightLineDouble(((ts.Layout->SM_IND.x + 18) + i*10),((ts.Layout->SM_IND.y + 55 - BTM_MINUS - (!ts.show_wide_spectrum?0:3)) - 7),7,LCD_DIR_VERTICAL,col);
				}
			}
		}
	}
	// Draw meters
	UiDriver_UpdateTopMeterA(34);
	UiDriver_UpdateTopMeterA(0);
	UiDriver_UpdateBtmMeter(34, 34);
	UiDriver_UpdateBtmMeter(0, 34);

}

//*----------------------------------------------------------------------------
//* Function Name       : UiDriverUpdateTopMeterA
//* Object              : redraw indicator, same like upper implementation
//* Input Parameters    : but no hold
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
#define SMETER_MAX_LEVEL 33

enum
{
	METER_TOP = 0,
	METER_BTM,
	METER_NUM
};
typedef struct MeterState_s
{
	uint8_t last;
	uint8_t last_warn;
	uint8_t peak;
	uint32_t timer;
} MeterState;

static MeterState meters[METER_NUM];

static uint8_t SMmode;

/**
 * Displays a horizontal meter bar as dash segments.
 *
 * @param val value to display. Max value (100%) is SMETER_MAX_LEVEL, higher values are cut off.
 * @param warn At which value shall value is in red color. 0 to disable.
 * @param color_norm default, non-warning color
 * @param meterId index of the meter in the internal meter data storage.
 */
static void UiDriver_UpdateMeter(uchar val, uchar warn, uint32_t color_norm, uint8_t meterId)
{
    assert(meterId < METER_NUM);

	uint8_t from_warn = 255;

	// limit meter
	if(val > SMETER_MAX_LEVEL)
	{
		val = SMETER_MAX_LEVEL;
	}
	if (warn == 0)
	{
		warn = SMETER_MAX_LEVEL+1;    // never warn if warn == 0
	}

	if(warn != meters[meterId].last_warn)
	{
		if (warn < meters[meterId].last_warn)
		{
			from_warn = warn;
		}
		else
		{
			from_warn = meters[meterId].last_warn;
		}
	}

    const uint16_t ypos = meterId==METER_TOP?(ts.Layout->SM_IND.y + 28 - (!ts.show_wide_spectrum?0:2)):(ts.Layout->SM_IND.y + 51 - BTM_MINUS - (!ts.show_wide_spectrum?0:3));
    // segment line
    const uint8_t v_s = 3; // segment length

    // Peak indication
    if(ts.peak_ind_tune != 0 && meterId == 0) // Draw just for S/PWR meter, if peak ind. is ON
    {
        // reset p.i. on mode change
        uint8_t SMmode_new = 0 + !ts.show_wide_spectrum?0:1 + (ts.txrx_mode == TRX_MODE_RX)?2:0;
        if(SMmode_new != SMmode)
        {
            meters[meterId].timer = ts.sysclock;//0;
            SMmode = SMmode_new;
        }
        if(val >= meters[meterId].peak)
        {
            meters[0].peak = val;
            meters[0].timer = ts.sysclock + (ts.peak_ind_tune * 25);
        }
        if(meters[0].timer <= ts.sysclock)
        {
            // Erase peak indicator
            UiLcdHy28_DrawStraightLineTriple(((ts.Layout->SM_IND.x + 18) + meters[meterId].peak*(v_s + 2)),(ypos - v_s),v_s,LCD_DIR_VERTICAL,Grid);
            meters[meterId].peak = 0;
        }
        else
        {
            // Show peak indicator
            UiLcdHy28_DrawStraightLineTriple(((ts.Layout->SM_IND.x + 18) + meters[meterId].peak*(v_s + 2)),(ypos - v_s),v_s,LCD_DIR_VERTICAL,color_norm);
        }
    }
    // The end of the peak indication block

	if(val != meters[meterId].last || from_warn != 255)
//	if(val != meters[meterId].last || from_warn != 255 || ((meterId = 0) && (meters[0].timer <= ts.sysclock)))
	{
	    uint8_t from, to;

		// decide if we need to draw more boxes or delete some
		if (val > meters[meterId].last)
		{
			// we will draw more active boxes
			from = meters[meterId].last;
			to = val+1;

		}
		else
		{
			from = val;
			to   = meters[meterId].last+1;
		}
		if (from_warn < from)
		{
			from = from_warn;
		}

		// Draw indicator
		// we never draw a zero, so we start from 1 min
		if (from == 0)
		{
			from = 1;
		}

	    uint32_t col = color_norm;
	    // at start: use the requested value color

	    // the code below is responsible for location and size of the dash segments
        // and the drawing

        // which of the 2 positions our bar will have
//      const uint16_t ypos = meterId==METER_TOP?(ts.Layout->SM_IND.y + 28):(ts.Layout->SM_IND.y + 51 - BTM_MINUS);
//	    const uint16_t ypos = meterId==METER_TOP?(ts.Layout->SM_IND.y + 28 - (!ts.show_wide_spectrum?0:2)):(ts.Layout->SM_IND.y + 51 - BTM_MINUS - (!ts.show_wide_spectrum?0:3));
//        // segment line
//        const uint8_t v_s = 3; // segment length

		for(int i = from; i < to; i++)
		{
			if (i>val)
			{
				col = Grid;    // switch to delete color
			}
			if((i >= warn) && warn && col != Grid)    // is level above "warning" color? (is "warn" is zero, disable warning)
			{
				col = Red2;                 // yes - display values above that color in red
			}

			UiLcdHy28_DrawStraightLineTriple(((ts.Layout->SM_IND.x + 18) + i*(v_s + 2)),(ypos - v_s),v_s,LCD_DIR_VERTICAL,col);
		}

//		// Peak indication
//		if(ts.peak_ind_tune != 0 && meterId == 0) // Draw just for S/PWR meter, if peak ind. is ON
//		{
//			// reset p.i. on mode change
//			uint8_t SMmode_new = 0 + !ts.show_wide_spectrum?0:1 + (ts.txrx_mode == TRX_MODE_RX)?2:0;
//			if(SMmode_new != SMmode)
//			{
//				meters[meterId].timer = ts.sysclock;//0;
//				SMmode = SMmode_new;
//			}
//			if(val >= meters[meterId].peak)
//			{
//				meters[0].peak = val;
//				meters[0].timer = ts.sysclock + (ts.peak_ind_tune * 25);
//			}
//			if(meters[0].timer <= ts.sysclock)
//			{
//				// Erase peak indicator
//				UiLcdHy28_DrawStraightLineTriple(((ts.Layout->SM_IND.x + 18) + meters[meterId].peak*(v_s + 2)),(ypos - v_s),v_s,LCD_DIR_VERTICAL,Grid);
//				meters[meterId].peak = 0;
//			}
//			else
//			{
//				// Show peak indicator
//				UiLcdHy28_DrawStraightLineTriple(((ts.Layout->SM_IND.x + 18) + meters[meterId].peak*(v_s + 2)),(ypos - v_s),v_s,LCD_DIR_VERTICAL,color_norm);
//			}
//		}
//		// The end of the peak indication block

		meters[meterId].last = val;
		meters[meterId].last_warn = warn;
	}
}


static void UiDriver_UpdateTopMeterA(uchar val)
{
	ulong clr;
	UiMenu_MapColors(ts.meter_colour_up,NULL,&clr);
	UiDriver_UpdateMeter(val,0,clr,METER_TOP);
}

/**
 * @brief updates the lower meter
 * @param val the value to display, max value is S_METER_MAX, min is 0, values will be limited to range
 * @param warn the level value from which the meter is going to show warning indication
 */
static void UiDriver_UpdateBtmMeter(float val, uchar warn)
{
	ulong clr;
	UiMenu_MapColors(ts.meter_colour_down,NULL,&clr);
	if (val < 0)
	{
		val = 0;
	}
	if (val > S_METER_MAX)
	{
		val = S_METER_MAX;
	}
	UiDriver_UpdateMeter(val,warn,clr,METER_BTM);
}

// FIXME: Move to RadioManagement()
void UiDriver_InitBandSet()
{
    // TODO: Do this setting based on the detected RF board capabilities
    // set the enabled bands
    uint32_t min_osc = osc->getMinFrequency();
    uint32_t max_osc = osc->getMaxFrequency();

    // first we enabled all bands for rx based on the reported tuning frequency range
    // please note, that this may enabled bands not really usable
    // as other limitations of the hardware such as lpf/bpf limits
    // etc. may apply. For instance, the standard MCHF RF has RX BPF/LPF
    // limiting RX above 32 Mhz.

    // the PA itself has its own limits which are checked before transmitting
    // so we don't care here about these limits

    for(int i = 0; i < MAX_BANDS; i++)
    {
        const BandInfo* bi = RadioManagement_GetBandInfo(i);
        band_enabled[i] = ((bi->tune >= min_osc) && ((bi->size + bi->tune) <= max_osc));
    }
    // For Robin bagnall
//    band_enabled[BAND_MODE_4] = true;
//    band_enabled[BAND_MODE_6] = true;

    switch (ts.rf_board)
    {
    case FOUND_RF_BOARD_MCHF:
        // here you can enable or disable based on additional hardware capabilities
        // but this should only be used with care
        /*
        band_enabled[BAND_MODE_23] = false;
        band_enabled[BAND_MODE_70] = false;
        band_enabled[BAND_MODE_2] = false;
        band_enabled[BAND_MODE_4] = false;
        band_enabled[BAND_MODE_6] = false;
        band_enabled[BAND_MODE_630] = false;
        band_enabled[BAND_MODE_2200] = false;
        */
        break;
    case FOUND_RF_BOARD_OVI40:
        /*
        band_enabled[BAND_MODE_23] = false;
        band_enabled[BAND_MODE_70] = false;
        */
        break;
    }

	const char* test = Board_BootloaderVersion();
	char res = 0;
	for (int i=0; i<20;i++)			// find last character in bootloader string
	{
		if(test[i] == 0)
		{
			res = test[i-1];
			break;
		}
	}
	if(res == 0x61)					// if it is an "a" ==> DF8OE version, enable all bands
	{
  		for(int i = 0; i < MAX_BANDS; i++)
  		{
      		band_enabled[i] = true;
  		}
	}
}

//*----------------------------------------------------------------------------
//* Function Name       : UiDriverInitFrequency
//* Object              : set default values, some could be overwritten later
static void UiDriver_InitFrequency()
{
	// Clear band values array
	for(int i = 0; i < MAX_BANDS; i++)
	{
		vfo[VFO_A].band[i].dial_value = 0xFFFFFFFF;	// clear dial values
		vfo[VFO_A].band[i].decod_mode = DEMOD_USB; 	// clear decode mode
        vfo[VFO_A].band[i].digital_mode = DigitalMode_None;   // clear digital mode
		vfo[VFO_B].band[i].dial_value = 0xFFFFFFFF;  // clear dial values
		vfo[VFO_B].band[i].decod_mode = DEMOD_USB;   // clear decode mode
        vfo[VFO_B].band[i].digital_mode = DigitalMode_None;   // clear digital mode
        vfo[VFO_B].band[i].dial_delta = 0;
	}

	// Lower bands default to LSB mode
	// TODO: This needs to be checked, some even lower bands have higher numbers now
	for(int i = 0; i < 4; i++)
	{
		vfo[VFO_A].band[i].decod_mode = DEMOD_LSB;
		vfo[VFO_B].band[i].decod_mode = DEMOD_LSB;
	}

	// Init frequency publics(set diff values so update on LCD will be done)
	df.tune_old 	= 0;
	df.tune_new 	= 3500001;
	df.selected_idx = T_STEP_1KHZ_IDX; 		// 1 Khz startup step
	df.tuning_step	= tune_steps[df.selected_idx];
	df.temp_factor	= 0;
	df.temp_factor_changed = false;
	df.temp_enabled = 0;		// startup state of TCXO

	UiDriver_InitBandSet();
}

/**
 * @brief Checks in which band the current frequency lies and updates display only if changed
 *
 * @param freq frequency in Hz
 * @returns band index (0 - (MAX_BANDS-1))
 */

//void UiDriver_DisplayBandForFreq(uint32_t freq)
void UiDriver_DisplayBandForFreq(uint32_t freq, bool force)
{
	// here we maintain our local state of the last band shown
    const BandInfo* band = RadioManagement_GetBand(freq);

    // yes, did the tx band actually change?
    // or are we in the generic band (i.e. outside tx bands)
//	if(band != ts.band_effective || RadioManagement_IsGenericBand(band))
	if(band != ts.band_effective || RadioManagement_IsGenericBand(band) || force)
	{
		UiDriver_DisplayBand(band);    // yes, update the display with the current band
		UiDriver_HandlePowerLevelChange(band, ts.power_level); // also validate power level if band changes
	}
	ts.band_effective = band;
    if (ts.lotx_dacs_present)
    {
        LO_TX_SUPR_DAC_GetBand(freq);
    }
}




/*
 * @brief Changes the tune frequency according to mode and other settings
 * @param dial_freq The desired dial frequency in Hz (not the tune frequency of the LO)
 * @returns true if the change was executed  (even if it is not tunable freq), false if the change is pending
 */
/*
 * @brief Check if a frequency is tunable
 * @returns SI570_OK, SI570_LARGE_STEP, SI570_TUNE_LIMITED if ok, SI570_TUNE_IMPOSSIBLE if not OK
 */
/*
 * @brief Used to update the individual vfo displays, not meant to be called directly except when changing LO
 * @brief parameters (in this case use (true,0)), use UiDriver_FrequencyUpdateLOandDisplay(full_update) instead
 *
 * @param force_update true = unconditionally update synthesizer EVEN IF frequency did not change
 * @param mode  =0 automatic, 1=force large, 2=force small, upper (RX), 3 = small, lower (TX)
 *
 * WARNING:  If called with "mode = 3", you must ALWAYS call again with "mode = 2" to reset internal variables.
 */
/*
 * @brief change LO freq to match df.tune_new freq according to mode without updating the ui
 *
 * @param trx_mode The mode which the frequency is being used for (TRX_MODE_TX/TRX_MODE_RX)
 */
void UiDriver_UpdateFrequency(bool force_update, enum UpdateFrequencyMode_t mode)
{

	// FIXME: Don't like the handling of lo_result if in Split mode and transmitting
	uint32_t		dial_freq;
	Oscillator_ResultCodes_t       lo_result = OSC_OK;
	bool        lo_change_not_pending = true;

	if(mode == UFM_SMALL_TX)
		// are we updating the TX frequency (small, lower display)?
	{
		dial_freq = RadioManagement_GetTXDialFrequency();

		// we check with the si570 code if the frequency is tunable, we do not tune to it.
		lo_result = RadioManagement_ValidateFrequencyForTX(dial_freq);

	}
	else
	{
		dial_freq = df.tune_new;

		lo_change_not_pending =  RadioManagement_ChangeFrequency(force_update, dial_freq, ts.txrx_mode);
		lo_result = ts.last_lo_result;   // use last ts.lo_result
	}

	if (mode == UFM_SMALL_RX && ts.txrx_mode == TRX_MODE_TX )
		// we are not going to show the tx frequency here (aka dial_freq) so we cannot use dial_freq
	{
		dial_freq = RadioManagement_GetRXDialFrequency();

		// we check with the si570 code if the frequency is tunable, we do not tune to it.
		// lo_result = RadioManagement_ValidateFrequencyForTX(dial_freq);
	}

	// ALL UI CODE BELOW
	{
		uint32_t clr;

		if (lo_change_not_pending)
		{

			if (mode != UFM_SMALL_TX)
			{
//				UiDriver_DisplayBandForFreq(dial_freq);
				UiDriver_DisplayBandForFreq(dial_freq, false);
				// check which band in which we are currently tuning and update the display

//				UiDriver_UpdateLcdFreq(RadioManagement_GetRXDialFrequency() ,White, UFM_SECONDARY);
				UiDriver_UpdateLcdFreq(RadioManagement_GetRXDialFrequency() ,ts.cmem_colour, UFM_SECONDARY);
				// set mode parameter to UFM_SECONDARY to update secondary display (it shows real RX frequency if RIT is being used)
				// color argument is not being used by secondary display
			}

			switch(lo_result)
			{
			case OSC_TUNE_IMPOSSIBLE:
				clr = Orange; // Color in orange if there was a problem setting frequency
				break;
			case OSC_TUNE_LIMITED:
				clr = Yellow; // Color in yellow if there was a problem setting frequency exactly
				break;
			case OSC_LARGE_STEP:
			case OSC_OK:
				clr = White;
				break;
			default:
				clr = Red; // a serious error happened, i.e. I2C not working etc.
			}
		}
		else
		{
			// we did not execute the change, so we show the freq in Blue.
			// this will turn into the appropriate color the moment the tuning
			// happens.
			// Use white in releases, many complained about the  Blue digits
			clr = White; // Blue;
		}
		// Update frequency display
		UiDriver_UpdateLcdFreq(dial_freq, clr, mode);
	}

	// Contest mode - F4IYT
	bool change_cmem_colour = false;
    if ((ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON) && ts.vfo_mem_flag)
    {
		uint32_t freq_calc = RadioManagement_GetRXDialFrequency() + (ts.dmod_mode == DEMOD_CW ? RadioManagement_GetCWDialOffset() : 0 );
		if(ts.pos_id_contest)
		{
			for(int idpos = 0; idpos <= ts.pos_id_contest; idpos++)
			{
				if ((freq_calc >= (ts.matrix_contest[idpos] - 100)) && (freq_calc <= (ts.matrix_contest[idpos] + 100)))
				{
//					UiDriver_UpdateLcdFreq(RadioManagement_GetRXDialFrequency() ,Red, UFM_SECONDARY);
					ts.cmem_colour = Red;
					change_cmem_colour = true;
					break;
				}
			}
		}
		if (ts.pos_id_contesttry)
		{
			for(int idpos = 0; idpos <= ts.pos_id_contesttry; idpos++)
			{
				if ((freq_calc >= (ts.matrix_contesttry[idpos] - 100)) && (freq_calc <= (ts.matrix_contesttry[idpos] + 100)))
				{
//					UiDriver_UpdateLcdFreq(RadioManagement_GetRXDialFrequency() ,Green, UFM_SECONDARY);
					if(ts.matrix_contesttry[idpos])
					{
					ts.cmem_colour = Green;
					change_cmem_colour = true;
					break;
					}
				}
			}
		}
		if(!change_cmem_colour)
		{
			ts.cmem_colour = White;
		}
    }
    else
    {
    	ts.cmem_colour = White;
    }
    //
}



//static void UiDriver_UpdateFreqDisplay(uint32_t dial_freq, uint32_t pos_x_loc, uint32_t pos_y_loc, uint16_t color, uint8_t digit_font)
static void UiDriver_UpdateFreqDisplay(uint64_t dial_freq, uint32_t pos_x_loc, uint32_t pos_y_loc, uint16_t color, uint8_t digit_font)
{
	uint8_t MAX_DIGITS_T = MAX_DIGITS; // 11

	if(!(ts.xverter_mode & 0xf))
	{
	    MAX_DIGITS_T = 9;
	}

//	uint8_t MAX_DIGITS_T = !ts.show_wide_spectrum?MAX_DIGITS:MAX_DIGITS_WS;
    uint8_t digits[MAX_DIGITS_T];
    uint8_t last_non_zero = 0;
    const uint8_t font_width = UiLcdHy28_TextWidth("0",digit_font);

    // Terminate string for digit
    char digit[2];
    digit[1] = 0;

    // calculate the digits
//    uint32_t dial_freq_temp = dial_freq;
    uint64_t dial_freq_temp = dial_freq;
    for (uint32_t idx = 0; idx < MAX_DIGITS_T; idx++)
    {
        digits[idx] = dial_freq_temp % 10;
        dial_freq_temp /= 10;
        if (digits[idx] != 0) last_non_zero = idx;
    }

    const uint16_t group_space = 3* font_width + font_width/2;

    const uint16_t x_right = pos_x_loc + (9* font_width);

    for (uint32_t idx = 3; idx < MAX_DIGITS_T; idx+=3)
    {
        bool noshow = last_non_zero < idx;
        int digit_group = idx / 3; // we group every 3 digits

        digit[0] = '.';
        uint32_t dot_clr = noshow?Black:color;
        if (digit_font != 5)
        {
            UiLcdHy28_PrintText(x_right - (digit_group * group_space) + font_width/2 + 1  ,pos_y_loc,digit,dot_clr,Black,digit_font);
        }
        else
        {
            UiLcdHy28_PrintText(x_right - (digit_group * group_space) + font_width ,pos_y_loc,digit,dot_clr,Black,digit_font);
        }
    }

    for (uint32_t idx = 0; idx < MAX_DIGITS_T; idx++)
    {
        int digit_group = idx / 3; // we group every 3 digits
        int digit_idx = idx % 3;

        bool noshow = idx > last_non_zero;
        // don't show leading zeros, except for the 0th digits
        digit[0] = noshow?'0':0x30 + (digits[idx] & 0x0F);
        uint32_t digit_clr = noshow?Black:color;
        // Update segment
        UiLcdHy28_PrintText(x_right -  digit_idx * font_width - (digit_group * group_space), pos_y_loc, digit, digit_clr, Black, digit_font);
    }
}

//*----------------------------------------------------------------------------
//* Function Name       : UiDriverUpdateLcdFreq
//* Object              : this function will split LCD freq display control
//* Object              : and update as it is 7 segments indicator
//* Input Parameters    : freq=freq (Hz), color=color, mode: 0 = auto, 1= force normal (large digits), 2= force upper, small, 3 = force lower, small, 4 = secondary display
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_UpdateLcdFreq(uint32_t dial_freq, uint16_t color, uint16_t mode)
{
//	uint8_t		digit_font;

	if(ts.frequency_lock)
	{
		// Frequency is locked - change color of display
		color = Grey;
	}

	if(mode == UFM_AUTOMATIC)
	{
	    mode = is_splitmode() ? UFM_SMALL_RX : UFM_LARGE;
		// in "split" mode?
		// yes - update upper, small digits (receive frequency)
		// no  - large, normal-sized digits
	}

//	uint32_t disp_freq;
    uint64_t disp_freq;

    // if in transverter mode, main display shows translated frequencies  in blue2
    // the secondary display will always show the real untranslated frequency

//	if(ts.xverter_mode)	 	// transverter mode active?
    if(RadioManagement_Transverter_IsEnabled() && mode != UFM_SECONDARY)        // transverter mode active?
	{
//		disp_freq = dial_freq * ts.xverter_mode + ts.xverter_offset;
//		// yes - scale by LO multiplier and add transverter frequency offset
//		if(ts.xverter_mode && mode != UFM_SECONDARY)	// if in transverter mode, frequency is blue2 unless we do the secondary display
//		{
//			color = Blue2;
//		}
        uint8_t txrx_mode = (mode == UFM_LARGE) ?
                ts.txrx_mode
                :
                (mode == UFM_SMALL_TX ? TRX_MODE_TX : TRX_MODE_RX);

        disp_freq = RadioManagement_Transverter_GetFreq(dial_freq, txrx_mode);
        // scale by LO multiplier and add transverter frequency offset
        color = Blue2;
	}
	else
	{
	    disp_freq = dial_freq;
	}

	// Handle frequency display offset in "CW RX" modes
	if(ts.dmod_mode == DEMOD_CW)	 		// In CW mode?
	{
		switch(ts.cw_offset_mode)
		{
		case CW_OFFSET_LSB_RX:	// Yes - In an LSB mode with display offset?
		case CW_OFFSET_USB_RX:	// In a USB mode with display offset?
		case CW_OFFSET_AUTO_RX:	// in "auto" mode with display offset?
			if(ts.cw_lsb)
			{
				disp_freq -= ts.cw_sidetone_freq;		// yes - LSB - lower display frequency by sidetone amount
			}
			else
			{
				disp_freq += ts.cw_sidetone_freq;		// yes - USB - raise display frequency by sidetone amount
			}
			break;
		}
	}

    uint16_t       pos_y_loc;
    uint16_t       pos_x_loc;
    uint8_t        digit_font;

	switch(mode)
	{
	case UFM_SMALL_RX:
		digit_font = 0;
		pos_y_loc = ts.Layout->TUNE_FREQ.y;
		pos_x_loc = ts.Layout->TUNE_SPLIT_FREQ_X;
		break;
	case UFM_SMALL_TX:					// small digits in lower location
		digit_font = 0;
		pos_y_loc = ts.Layout->TUNE_SPLIT_FREQ_Y_TX;
		pos_x_loc = ts.Layout->TUNE_SPLIT_FREQ_X;
		break;
	case UFM_SECONDARY:
		digit_font = 0;
		pos_y_loc = ts.Layout->TUNE_SFREQ.y;
		pos_x_loc = ts.Layout->TUNE_SFREQ.x;
		break;
	case UFM_LARGE:
	default:			// default:  normal sized (large) digits
#ifdef USE_8bit_FONT
		digit_font=ts.FreqDisplayFont==0?1:5;
#else
		digit_font = 1;
#endif
		pos_y_loc = ts.Layout->TUNE_FREQ.y;
		pos_x_loc = ts.Layout->TUNE_FREQ.x;
	}

	// in SAM mode, never display any RIT etc., but
	// use small display for display of the carrier frequency that the PLL has locked to
	if(ts.dmod_mode == DEMOD_SAM && (mode == UFM_SMALL_RX || mode == UFM_SECONDARY))
	{
		digit_font = 0;
		pos_y_loc = ts.Layout->TUNE_SFREQ.y;
		pos_x_loc = ts.Layout->TUNE_SFREQ.x;
		disp_freq += ads.carrier_freq_offset;
		color = Yellow;
	}

    UiDriver_UpdateFreqDisplay(disp_freq, pos_x_loc, pos_y_loc, color, digit_font);
}

//*----------------------------------------------------------------------------
//* Function Name       : UiDriverChangeTuningStep
//* Object              : Change tunning step
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
void UiDriver_ChangeTuningStep(uchar is_up)
{
    const int8_t step = is_up ? +1 : -1;

	int32_t idx = df.selected_idx;

	int8_t idx_limit = T_STEP_MAX_STEPS;

	if((!ts.xvtr_adjust_flag) && (!ts.xverter_mode))
	{
		// are we NOT in "transverter adjust" or transverter mode *NOT* on?
		idx_limit = T_STEP_1MHZ_IDX;
	}

	idx = (idx + step + idx_limit) % idx_limit;

	// 9kHz step only on MW and LW, skip otherwise
	if(idx == T_STEP_9KHZ_IDX && ((df.tune_old) > 1600001))
	{
			idx+= step;
	}

	df.tuning_step	= tune_steps[idx];
	df.selected_idx = idx;

	// Update step on screen
	UiDriver_DisplayFreqStepSize();

}

static void UiDriver_ShowTxErrorMessages(void)
{
    // if there is no power factor ( == no output power)
    // or effective bias is 0 (== no or very distorted output)
    // inform operator
//	const bool no_tx_power = ts.tx_power_factor == 0;
	const bool no_tx_power = ts.tx_power_factor == 0.0;
	const bool no_bias = (ts.dmod_mode != DEMOD_CW && ts.pa_bias == 0) || (ts.dmod_mode == DEMOD_CW && ts.pa_cw_bias == 0 && ts.pa_bias == 0);
	

    if (RadioManagement_IsTxDisabled() == false && (no_tx_power || no_bias))
    {
        UiDriver_DisplayMessageStart();
		int display_time = 3000;
		
        const uint16_t scope_middle_y = sd.Slayout->full.h/2+sd.Slayout->full.y;
        
		const char* txp = NULL;
		
		if (no_bias && no_tx_power) 
		{ 
			txp = "No TX power and no bias.\nCheck PA calibration!";
		} 
		else if (no_bias) 
		{
			txp = "PA BIAS is 0.\nAdjust Bias before TX!";
		} 
		else if (no_tx_power)
		{
			txp = "PA TX Power Factor is 0.\nAdjust TX power for band!";
		}
		
		if(ts.menu_mode)
		{
			// we assume tune is used for power calibration when in menu mode
			if (ts.tune)
			{
				// we disable display of error message if bias is set.
				if (no_bias == false)
				{
					txp = NULL;
				}
				else
				{
					txp = "PA BIAS is 0.\nAdjust Bias before TX!";
				}
			}
			else
			{
				// shorter display time 
				display_time = 1000;
			}
		}
    	
		if (display_time > 0 && txp != NULL)
		{
        	UiLcdHy28_PrintTextCentered(sd.Slayout->full.x, scope_middle_y-6, sd.Slayout->full.w,txp,Red,Black,0);

        	HAL_Delay(display_time);
		}
        UiDriver_DisplayMessageStop();
    }
    else if(ts.expflags1 & EXPFLAGS1_CLEAR_PAN_ON_TX)
    {
    	UiSpectrum_Clear();
//    	UiLcdHy28_PrintText(140, 160, "TX", Red, Black, 5);
    	UiLcdHy28_PrintTextCentered(sd.Slayout->full.x, 160, sd.Slayout->full.w,"[TX]",Red,Black,1);
    }
}

#ifdef SDR_AMBER
static void UiDriver_ShowGreeting(uint8_t variant)
{
    UiDriver_DisplayMessageStart();
    int display_time = 6000;
    const uint16_t scope_middle_y = sd.Slayout->full.h/2+sd.Slayout->full.y-18;
    const char* txp = NULL;

    if (variant == 0)
    {
        txp = "Hello, I'm Amber.\nNice to meet and work for you.\n***\nFrom Russia with love";
    }
    else if (variant == 1)
    {
        txp = "Hello, I'm Axis.\nNice to meet and work for you.\n***\nFrom Russia with love";
    }

    if (display_time > 0 && txp != NULL)
    {
        UiLcdHy28_PrintTextCentered(sd.Slayout->full.x, scope_middle_y-6, sd.Slayout->full.w,txp,Magenta,Black,0);

        HAL_Delay(display_time);
    }
    UiDriver_DisplayMessageStop();
}
#endif

/*----------------------------------------------------------------------------
 * @brief Scans buttons 0-16:  0-15 are normal buttons, 16 is power button, 17 touch
 * @param button_num - 0-BUTTON_NUM - 1
 * @returns true if button is pressed
 */

static bool UiDriver_IsButtonPressed(uint32_t button_num)
{
	// FIXME: This is fragile code, as it depends on being called multiple times in short periods (ms)
	// This works, since regularily the button matrix is queried.
	UiLcdHy28_TouchscreenDetectPress();
	return Keypad_IsKeyPressed(button_num);
}

static void UiDriver_WaitForButtonPressed(uint32_t button_num)
{
    while (true)
    {
        Keypad_Scan();
        if (UiDriver_IsButtonPressed(button_num))
        {
            break;
        }
        HAL_Delay(20);
    }
}

void UiDriver_KeyboardProcessOldClicks()
{
    static uchar press_hold_release_delay = 0;

    Keypad_Scan(); // update all key states

    // State machine - processing old click
    if(ks.button_processed == false)
    {
        // State machine - click or release(debounce filter)
        if(ks.button_pressed == false)
        {
            // Scan logical button inputs (the result of the HW keypad scan)
            // this algorithm favors the pressed button with the lowest number
            for(int i = 0; i < BUTTON_NUM; i++)
            {
                if(UiDriver_IsButtonPressed(i))
                {
                    // Change state to clicked
                    ks.button_id      = i;
                    ks.button_pressed = true;
                    ks.button_released    = 0;
                    ks.button_just_pressed    = 0;
                    ks.debounce_time  = 0;
                    ks.debounce_check_complete    = 0;
                    ks.press_hold         = 0;
                    //printf("button_pressed %02x\n\r",ks.button_id);
                    // Exit, we process just one click at a time
                    break;
                }
            }
        }
        else if((ks.debounce_time >= BUTTON_PRESS_DEBOUNCE) && (!ks.debounce_check_complete))
        {
            if(UiDriver_IsButtonPressed(ks.button_id))        // button still pressed?
            {
                ks.button_just_pressed = 1; // yes!
                ks.debounce_check_complete = 1; // indicate that the debounce check was completed
            }
            else
            {
                ks.button_pressed = false;          // debounce incomplete, button released - cancel detection
            }
        }
        else if((ks.debounce_time >= BUTTON_HOLD_TIME) && (!ks.press_hold))     // press-and-hold processing
        {
            ks.button_processed = 1;                      // indicate that a button was processed
            ks.button_just_pressed = 0;                   // clear this flag so that the release (below) won't be detected
            ks.press_hold = 1;
            press_hold_release_delay = PRESS_HOLD_RELEASE_DELAY_TIME; // Set up a bit of delay for when press-and-hold is released
        }
        else if(ks.press_hold && (!UiDriver_IsButtonPressed(ks.button_id)))     // was there a press-and-hold and the button is now released?
        {
            if(press_hold_release_delay)                  // press-and-hold delay expired?
            {
                press_hold_release_delay--;                 // no - continue counting down before cancelling "press-and-hold" mode
            }
            else                              // Press-and-hold mode time expired!
            {
                ks.button_pressed = 0;          // reset and exit press-and-hold mode, this to prevent extraneous button-presses when using multiple buttons
                ks.button_released = 0;
                ks.press_hold = 0;
                ks.button_just_pressed = 0;
            }
        }
        else if(!UiDriver_IsButtonPressed(ks.button_id) && (!ks.press_hold))        // button released and had been debounced?
        {
            // Change state from click to released, and processing flag on - if the button had been held down adequately
            ks.button_pressed     = 0;
            ks.button_released    = 1;
            ks.button_processed   = 1;
            ks.button_just_pressed = 0;
            //printf("button_released %02x\n\r",ks.button_id);
        }
        //
        // Handle press-and-hold tuning step adjustment
        //
        if((ts.tune_step != STEP_PRESS_OFF) && (!ks.press_hold))     // are we in press-and-hold step size mode and did the button get released?
        {
            ts.tune_step = STEP_PRESS_OFF;                        // yes, cancel offset
            df.selected_idx = ts.tune_step_idx_holder;            // restore previous setting
            df.tuning_step    = tune_steps[df.selected_idx];
            UiDriver_DisplayFreqStepSize();
        }
    }
}



enum TRX_States_t
{
	TRX_STATE_TX_TO_RX,
	TRX_STATE_RX,
	TRX_STATE_RX_TO_TX,
	TRX_STATE_TX,
};

static void UiDriver_TxRxUiSwitch(enum TRX_States_t state)
{
	static uchar enc_one_mode =     ENC_ONE_MODE_AUDIO_GAIN;  // stores modes of encoder when we enter TX
	static uchar enc_two_mode =     ENC_TWO_MODE_RF_GAIN;    // stores modes of encoder when we enter TX
	static uchar enc_three_mode =   ENC_THREE_MODE_CW_SPEED;    // stores modes of encoder when we enter TX


	{
		if(state == TRX_STATE_RX_TO_TX)
		{

			UiDriver_DeleteSMeterLabels();
			UiDriver_DrawPowerMeterLabels();

			if((ts.flags1 & FLAGS1_TX_AUTOSWITCH_UI_DISABLE) == false)                // If auto-switch on TX/RX is enabled
			{
				// change display related to encoder one to TX mode (e.g. Sidetone gain or Compression level)
				enc_one_mode = ts.enc_one_mode;
				enc_two_mode = ts.enc_two_mode;
				enc_three_mode = ts.enc_thr_mode;

				// we reconfigure the encoders according to the currently selected mode
				// for now this is only relevant for CW
				if (ts.dmod_mode == DEMOD_CW)
				{
					ts.enc_one_mode = ENC_ONE_MODE_ST_GAIN;
					ts.enc_thr_mode = ENC_THREE_MODE_CW_SPEED;
				}
				else if (ts.dmod_mode == DEMOD_DIGI && ts.digital_mode == DigitalMode_BPSK)
				{
					ts.enc_one_mode = ENC_ONE_MODE_ST_GAIN;
					ts.enc_thr_mode = ENC_THREE_MODE_PSK_SPEED;
				}
				else // for all other modes we activate the compressor setting and input gain control
				{
					ts.enc_one_mode = ENC_ONE_MODE_CMP_LEVEL;
					ts.enc_thr_mode = ENC_THREE_MODE_INPUT_CTRL;
				}
			}

			// force redisplay of Encoder boxes and values
			UiDriver_RefreshEncoderDisplay();

			// if there is a need to tell the operator something related to
			// the tx mode (such as PA not configured correctly) we do this now.
			UiDriver_ShowTxErrorMessages();
		}
		else if (state == TRX_STATE_TX_TO_RX)
		{

			UiDriver_DeleteSMeterLabels();
			UiDriver_DrawSMeterLabels();
			if((ts.flags1 & FLAGS1_TX_AUTOSWITCH_UI_DISABLE) == false)                // If auto-switch on TX/RX is enabled
			{
				ts.enc_one_mode = enc_one_mode;
				ts.enc_two_mode = enc_two_mode;
				ts.enc_thr_mode = enc_three_mode;
			}

			// force redisplay of Encoder boxes and values
			UiDriver_RefreshEncoderDisplay();
			if(ts.expflags1 & EXPFLAGS1_CLEAR_PAN_ON_TX)
			{
				if(!ts.menu_mode) { UiSpectrum_Init(); }
			}
		}
	}

	UiDriver_UpdateBtmMeter(0,0);        // clear bottom meter of any outstanding indication when going back to RX
	if((ts.menu_mode))              // update menu when we are (or WERE) in MENU mode
	{
#ifndef SDR_AMBER_480_320
	#ifndef	OVI40_MOD_480_320
		if(ts.show_wide_spectrum) { UiLcdHy28_PrintTextCentered(0, 175, 79," SETUP  ",Blue,Black,0); }
	#else
		UiLcdHy28_PrintTextCentered(0, 205, 130," SETUP  ",Blue,Black,0);
	#endif
#else
		UiLcdHy28_PrintTextCentered(0, 205, 130," SETUP  ",Blue,Black,0);
#endif
		UiMenu_RenderMenu(MENU_RENDER_ONLY);
	}
}

/**
 * adjust volume and return to RX from TX and other time-related functions,
 * has to be called regularly
 */
static void UiDriver_TimeScheduler()
{
	static bool	 audio_spkr_volume_update_request = true;
	static bool  audio_spkr_delayed_unmute_active = false;

	static bool old_squelch = 0;	// used to detect change-of-state of squelch
	static bool old_tone_det = 0;	// used to detect change-of-state of tone decoder
	static bool old_tone_det_enable = 0;	// used to detect change-of-state of tone decoder enabling
	static bool old_burst_active = 0;		// used to detect state of change of tone burst generator
	static bool startup_done_flag = 0;
	static enum TRX_States_t last_state = TRX_STATE_RX; // we assume everything is
	enum TRX_States_t state;



	// let us figure out if we are in a stable state or if this
	// is the first run after a mode change
	if (ts.txrx_mode == TRX_MODE_TX)
	{
		if (last_state != TRX_STATE_TX)
		{
			state = TRX_STATE_RX_TO_TX;
		}
		else
		{
			state = TRX_STATE_TX;
		}
		last_state = TRX_STATE_TX;
	}
	else
	{
		if (last_state != TRX_STATE_RX)
		{
			state = TRX_STATE_TX_TO_RX;
		}
		else
		{
			state = TRX_STATE_RX;
		}
		last_state = TRX_STATE_RX;
	}


	/*** RX MODE ***/
	if(ts.txrx_mode == TRX_MODE_RX)
	{
		if (state == TRX_STATE_TX_TO_RX)
		{
			audio_spkr_delayed_unmute_active = true;
		}

		if(audio_spkr_delayed_unmute_active  && ts.audio_spkr_unmute_delay_count == 0)	 	// did timer hit zero
		{
			audio_spkr_delayed_unmute_active = false;
			audio_spkr_volume_update_request = true;
		}



		audio_spkr_volume_update_request |= ts.rx_gain[RX_AUDIO_SPKR].value != ts.rx_gain[RX_AUDIO_SPKR].value_old;


		if( audio_spkr_volume_update_request)	 	// in normal mode - calculate volume normally
		{

			ts.rx_gain[RX_AUDIO_SPKR].value_old = ts.rx_gain[RX_AUDIO_SPKR].value;
			ts.rx_gain[RX_AUDIO_SPKR].active_value = 1;		// software gain not active - set to unity
			if(ts.rx_gain[RX_AUDIO_SPKR].value <= CODEC_SPEAKER_MAX_VOLUME)  				// Note:  Gain > 16 adjusted in audio_driver.c via software
			{
				Codec_VolumeSpkr((ts.rx_gain[RX_AUDIO_SPKR].value));

			}
			else  	// are we in the "software amplification" range?
			{
				Codec_VolumeSpkr(CODEC_SPEAKER_MAX_VOLUME);		// set to fixed "maximum" gain
				ts.rx_gain[RX_AUDIO_SPKR].active_value = (((float32_t)ts.rx_gain[RX_AUDIO_SPKR].value)/2.5)-5.35;	// to float
			}

			audio_spkr_volume_update_request = false;
		}

		// update the on-screen indicator of squelch/tone detection (the "FM" mode text) if there is a change of state of squelch/tone detection
		if((old_squelch != ads.fm_conf.squelched)
				|| (old_tone_det != ads.fm_conf.subaudible_tone_detected)
				|| (old_tone_det_enable != (bool)ts.fm_subaudible_tone_det_select))       // did the squelch or tone detect state just change?
		{

			UiDriver_DisplayDemodMode();                           // yes - update on-screen indicator to show that squelch is open/closed
			old_squelch = ads.fm_conf.squelched;
			old_tone_det = ads.fm_conf.subaudible_tone_detected;
			old_tone_det_enable = (bool)ts.fm_subaudible_tone_det_select;
		}
	}

	/*** TX MODE ONLY ***/
	if(ts.txrx_mode == TRX_MODE_TX)
	{

		if((state == TRX_STATE_RX_TO_TX))
		{
			// we just switched to TX
			if((ts.dmod_mode != DEMOD_CW))        // did we just enter TX mode in voice mode?
			{
				ads.alc_val = 1;    // re-init AGC value
				ads.peak_audio = 0; // clear peak reading of audio meter
			}
		}


		// Has the timing for the tone burst expired?
		if(ts.sysclock > ts.fm_tone_burst_timing)
		{
			ads.fm_conf.tone_burst_active = 0;               // yes, turn the tone off
		}

		if(ads.fm_conf.tone_burst_active != old_burst_active)       // did the squelch or tone detect state just change?
		{
			UiDriver_DisplayDemodMode();                           // yes - update on-screen indicator to show that tone burst is on/off
			old_burst_active = ads.fm_conf.tone_burst_active;
		}
	}

	/*** TX+RX STATE CHANGE ONLY ***/
	// if we do change modes, some visuals need an update
	if(state == TRX_STATE_RX_TO_TX || state == TRX_STATE_TX_TO_RX)
	{
		// now update display according to the changed state
		UiDriver_TxRxUiSwitch(state);
	}

	/*** ALWAYS ***/
	UiDriver_LcdBlankingProcessTimer();

	// This delays the start-up of the DSP for several seconds to minimize the likelihood that the LMS function will get "jammed"
	// and stop working.

	/*** ONCE AFTER STARTUP DELAY ***/
	if( startup_done_flag == false && (ts.sysclock > DSP_STARTUP_DELAY))       // has it been long enough after startup?
	{
		startup_done_flag = true;                  // set flag so that we do this only once

		UiDriver_DisplayEncoderTwoMode();

		audio_spkr_volume_update_request = 1;      // set unmute flag to force audio to be un-muted - just in case it starts up muted!
		Codec_MuteDAC(false);                      // make sure that audio is un-muted
	}
}


/*
 * Tells you which SSB Demod Mode is the preferred one for a given frequency in Hertz
 */
//*----------------------------------------------------------------------------
//* Function Name       : UiDriverChangeDemodMode
//* Object              : change demodulator mode
//* Input Parameters    : "noskip", if TRUE, disabled modes are to be included
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------

/**
 * This function is responsible for make the changes to the UI layout
 * as required for a give new mode, such as enabling the right set of encoder boxes etc.
 */
typedef struct
{
	int16_t encoder_modes[3];
} encoder_mode_store_t;

void UiDriver_SetDemodMode(uint8_t new_mode)
{
	RadioManagement_SetDemodMode(new_mode);
#if 0
	static encoder_mode_store_t demod_modes[] =
	{
			{ ENC_ONE_MODE_AUDIO_GAIN, ENC_TWO_MODE_RF_GAIN, ENC_THREE_MODE_RIT }, // USB, LSB,(S)AM,FM,FreeDV
			{ ENC_ONE_MODE_ST_GAIN, -1, ENC_THREE_MODE_CW_SPEED }, // CW
			{ ENC_ONE_MODE_RTTY_SPEED, ENC_TWO_MODE_RTTY_SHIFT, -1 }, // RTTY
	};
#endif

	DigiModes_TxBufferReset();
	switch(ts.dmod_mode)
	{
	case DEMOD_DIGI:
	{
		switch(ts.digital_mode)
		{
		case DigitalMode_RTTY:
		    DigiModes_Set_BufferConsumer( RTTY );
			if (ts.enc_one_mode != ENC_ONE_MODE_AUDIO_GAIN)
			{
				ts.enc_one_mode = ENC_ONE_MODE_RTTY_SPEED;
			}

			if (ts.enc_two_mode != ENC_TWO_MODE_RF_GAIN)
			{
				ts.enc_two_mode = ENC_TWO_MODE_RTTY_SHIFT;
			}
			break;

		case DigitalMode_BPSK:
		    DigiModes_Set_BufferConsumer( BPSK );
			if (ts.enc_thr_mode != ENC_THREE_MODE_RIT)
			{
				ts.enc_thr_mode = ENC_THREE_MODE_PSK_SPEED;
			}
			break;
		}
	}
	break;

	case DEMOD_CW:
	{
	    DigiModes_Set_BufferConsumer( CW );
		if (ts.enc_one_mode != ENC_ONE_MODE_AUDIO_GAIN)
		{
			ts.enc_one_mode = ENC_ONE_MODE_ST_GAIN;
		}
		if (ts.enc_thr_mode != ENC_THREE_MODE_RIT)
		{
			ts.enc_thr_mode = ENC_THREE_MODE_CW_SPEED;
		}
	}
	break;
	default:
		if (ts.enc_thr_mode != ENC_THREE_MODE_RIT)
		{
			ts.enc_thr_mode = ENC_THREE_MODE_INPUT_CTRL;
		}
		break;
	}
	UiDriver_UpdateDisplayAfterParamChange();
}

static void UiDriver_ChangeToNextDemodMode(bool select_alternative_mode)
{
	uint8_t new_mode = ts.dmod_mode;	// copy to local, so IRQ is not affected
	if (select_alternative_mode)
	{
		new_mode = RadioManagement_NextAlternativeDemodMode(new_mode);
	}
	else
	{
		new_mode = RadioManagement_NextNormalDemodMode(new_mode);
	}

	// TODO: We call this always, since we may have switched sidebands or the digital mode
	// if we would remember that, we would decide if to call this.
	UiDriver_SetDemodMode(new_mode);
}

/**
 * @brief band memory switch. Sets VFO to use the values of specified band memory. Does not store "old" values.
 *
 * @param vfo_sel	which VFO A/B to use
 * @param new_band_index
 */
void UiDriver_SelectBandMemory(uint16_t vfo_sel, uint8_t new_band_index)
{
	if (ts.expflags2 & EXPFLAGS2_TEMP_MEM_BW_MOD_BAND) // enabled temp. remember BW on mode & band
	{
		UiDriver_StoreBwData();
	}

		// TODO: There is a strong similarity to code in UiDriverProcessFunctionKeyClick around line 2053
		df.tune_new = vfo[vfo_sel].band[new_band_index].dial_value;	// Load value from VFO
		ts.iq_freq_delta = vfo[vfo_sel].band[new_band_index].dial_delta;

		bool new_lsb = RadioManagement_CalculateCWSidebandMode();

		uint16_t new_dmod_mode = vfo[vfo_sel].band[new_band_index].decod_mode;
		uint16_t new_digital_mode = vfo[vfo_sel].band[new_band_index].digital_mode;

		bool isNewDigitalMode = ts.digital_mode != new_digital_mode && new_dmod_mode == DEMOD_DIGI;


		// we need to mute here since changing bands may cause audible click/pops
		RadioManagement_MuteTemporarilyRxAudio();

		ts.digital_mode = new_digital_mode;

		if(ts.dmod_mode != new_dmod_mode || (new_dmod_mode == DEMOD_CW && ts.cw_lsb != new_lsb) || isNewDigitalMode)
		{
			// Update mode
			ts.cw_lsb = new_lsb;
			RadioManagement_SetDemodMode(new_dmod_mode);
		}

		// Finally update public flag by setting the
		// appropriate bandInfo
		ts.band = RadioManagement_GetBandInfo(new_band_index);

		if (ts.expflags2 & EXPFLAGS2_TEMP_MEM_BW_MOD_BAND) // enabled temp. remember BW on mode & band
		{
			UiDriver_LoadBwData();
		}

		UiDriver_UpdateDisplayAfterParamChange();    // because mode/filter may have changed
		UiVk_Redraw();		//virtual keypads call (refresh purpose)
}

/**
 * @brief checking band change.
 * @param idx is idx of band.
 */
bool UiDriver_NotLockedBand(uint32_t idx)
{
    if(((ts.expflags3 & EXPFLAGS3_BAND_OFF_160M) && (idx == BAND_MODE_160)) ||
        ((ts.expflags3 & EXPFLAGS3_BAND_OFF_80M) && (idx == BAND_MODE_80)) ||
        ((ts.expflags3 & EXPFLAGS3_BAND_OFF_60M) && (idx == BAND_MODE_60)) ||
        ((ts.expflags3 & EXPFLAGS3_BAND_OFF_40M) && (idx == BAND_MODE_40)) ||
        ((ts.expflags3 & EXPFLAGS3_BAND_OFF_30M) && (idx == BAND_MODE_30)) ||
        ((ts.expflags3 & EXPFLAGS3_BAND_OFF_20M) && (idx == BAND_MODE_20)) ||
        ((ts.expflags3 & EXPFLAGS3_BAND_OFF_17M) && (idx == BAND_MODE_17)) ||
        ((ts.expflags3 & EXPFLAGS3_BAND_OFF_15M) && (idx == BAND_MODE_15)) ||
        ((ts.expflags3 & EXPFLAGS3_BAND_OFF_12M) && (idx == BAND_MODE_12)) ||
        ((ts.expflags3 & EXPFLAGS3_BAND_OFF_10M) && (idx == BAND_MODE_10)) ||
        ((ts.expflags3 & EXPFLAGS3_BAND_OFF_6M) && (idx == BAND_MODE_6)))
    {
        return false;
    }
    return true;
}

/**
 * @brief initiate band change.
 * @param is_up select the next higher band, otherwise go to the next lower band
 */
static void UiDriver_ChangeBand(bool is_up)
{

	// Do not allow band change during TX
	if(ts.txrx_mode != TRX_MODE_TX)
	{
//		if (ts.expflags2 & EXPFLAGS2_TEMP_MEM_BW_MOD_BAND) // enabled temp. remember BW on mode & band
//		{
//			UiDriver_StoreBwData();
//		}

		vfo_name_t vfo_sel = get_active_vfo();

		uint8_t curr_band_index = ts.band->band_mode; // index in band table of currently selected band


		// Save old band values
		if(curr_band_index < (MAX_BANDS) && ts.cat_band_index == 255)
		{
			// Save dial, but only if we are not in "CAT mode"
//			vfo[vfo_sel].band[curr_band_index].dial_value = df.tune_old;
			vfo[vfo_sel].band[curr_band_index].dial_value = df.tune_new;
			vfo[vfo_sel].band[curr_band_index].decod_mode = ts.dmod_mode;
			vfo[vfo_sel].band[curr_band_index].digital_mode = ts.digital_mode;
		}
		else
		{
			ts.cat_band_index = 255;
		}

		uint8_t   new_band_index = curr_band_index;     // index of the new selected band
		// in case of no other band enabled, we stay in this band

		// we start checking the index following (is_up) or preceding (!is_up) the current one
		// until we reach an enabled band
		for (int idx  = 1; idx <= MAX_BANDS; idx++)
		{
		    uint32_t test_idx = (curr_band_index + ((is_up == true) ? idx : (MAX_BANDS-idx)))% MAX_BANDS;
#ifndef USE_MEMORY_MODE
//		    if (band_enabled[test_idx])
		    if (band_enabled[test_idx] && UiDriver_NotLockedBand(test_idx))
#endif
		    {
		        new_band_index = test_idx;
		        break; // we found the first enabled band following the current one
		    }
		}

		UiDriver_SelectBandMemory(vfo_sel, new_band_index);
	}
}


/**
 * @brief Read out the changes in the frequency encoder and initiate frequency change by setting a global variable.
 *
 * @returns true if a frequency change was detected and a new tuning frequency was set in a global variable.
 */
static bool UiDriver_CheckFrequencyEncoder()
{
	int 		pot_diff;
	bool		retval = false;
	int		enc_multiplier;
	static float 	enc_speed_avg = 0.0;  //keeps the averaged encoder speed
	int		delta_t, enc_speed;

	pot_diff = UiDriverEncoderRead(ENCFREQ);


	if (pot_diff != 0)
	{
		delta_t = ts.audio_int_counter;  // get ticker difference since last enc. change
		ts.audio_int_counter = 0;		 //reset tick counter

		UiDriver_LcdBlankingStartTimer();	// calculate/process LCD blanking timing

	}
	if (pot_diff != 0 &&
			ts.txrx_mode == TRX_MODE_RX
			&& ks.button_just_pressed == false
			&& ts.frequency_lock == false)
	{
		// allow tuning only if in rx mode, no freq lock,

#ifdef FAST_FREQ_ENC
	    delta_t *= 0.2; // pooling interval 2 ms instead of 10 ms
	    if (delta_t > 60)
#else
		if (delta_t > 300)
#endif
		{
			enc_speed_avg = 0;    //when leaving speedy turning set avg_speed to 0
		}

		enc_speed = div(4000,delta_t).quot*pot_diff;  // app. 4000 tics per second -> calc. enc. speed.

		if (enc_speed > 500)
		{
			enc_speed = 500;    //limit calculated enc. speed
		}
		if (enc_speed < -500)
		{
			enc_speed = -500;
		}

		enc_speed_avg = 0.1*enc_speed + 0.9*enc_speed_avg; // averaging to smooth encoder speed

		enc_multiplier = 1; //set standard speed

		if (ts.flags1 & FLAGS1_DYN_TUNE_ENABLE)   // check if dynamic tuning has been activated by touchscreen
		{
		    if (!(ts.expflags1 & EXPFLAGS1_SMOOTH_DYNAMIC_TUNE))        // Smooth dynamic tune is OFF
			{
				if ((enc_speed_avg > 80) || (enc_speed_avg < (-80)))
				{
					enc_multiplier = 10;    // turning medium speed -> increase speed by 10
				}

				if ((enc_speed_avg > 160) || (enc_speed_avg < (-160)))
				{
					enc_multiplier = 40;    //turning fast speed -> increase speed by 100
				}

				if ((enc_speed_avg > 300) || (enc_speed_avg < (-300)))
				{
					enc_multiplier = 100;    //turning fast speed -> increase speed by 100
				}
			}
            else
            {
                if      ((enc_speed_avg > 350) || (enc_speed_avg < (-350)))
                {
                    enc_multiplier = 100;    // turning medium speed -> increase speed by 100
                }
                else if ((enc_speed_avg > 250) || (enc_speed_avg < (-250)))
                {
                    enc_multiplier =  50;    //turning fast speed -> increase speed by 50
                }
                else if ((enc_speed_avg > 180) || (enc_speed_avg < (-180)))
                {
                    enc_multiplier =  12;    //turning fast speed -> increase speed by 12
                }
                else if ((enc_speed_avg >  90) || (enc_speed_avg < (- 90)))
                {
                    enc_multiplier =   6;    //turning fast speed -> increase speed by 6
                }
                else if ((enc_speed_avg >  45) || (enc_speed_avg < (- 45)))
                {
                    enc_multiplier =   3;    //turning fast speed -> increase speed by 3
                }
                else if ((enc_speed_avg >  30) || (enc_speed_avg < (- 30)))
                {
                    enc_multiplier =   2;    //turning fast speed -> increase speed by 2
                }
			}

			if ((df.tuning_step == 10000) && (enc_multiplier > 10))
			{
				enc_multiplier = 10;    //limit speed to 100000kHz/step
			}
			if ((df.tuning_step == 100000) && (enc_multiplier > 1))
			{
				enc_multiplier = 1;    //limit speed to 100000kHz/step
			}
		}


		// Finally convert to frequency incr/decr

//		if(pot_diff>0)
//		{
//			df.tune_new += (df.tuning_step * enc_multiplier);
//			//itoa(enc_speed,num,6);
//			//UiSpectrumClearDisplay();			// clear display under spectrum scope
//			//UiLcdHy28_PrintText(110,156,num,Cyan,Black,0);
//		}
//		else
//		{
//			df.tune_new -= (df.tuning_step * enc_multiplier);
//		}
//
//
//		if (enc_multiplier != 1)
//		{
//			df.tune_new = enc_multiplier*df.tuning_step * div((df.tune_new),enc_multiplier*df.tuning_step).quot;    // keep last digit to zero
//		}

		if (ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE)
		{
//		    int32_t freq_delta = ts.iq_freq_delta;
		    int32_t    freq_delta = ts.iq_freq_delta;
//		    int32_t    max = 12000 / (1 << sd.magnify);
		    int32_t    max = 18000 / (1 << sd.magnify);
		    if (pot_diff>0)
		    {
                freq_delta -= (df.tuning_step * enc_multiplier);

                if (freq_delta < -max)
                {
                    freq_delta = -max;
                }
                df.tune_new += df.tuning_step * enc_multiplier;
		    }
		    else
		    {
                freq_delta += (df.tuning_step * enc_multiplier);

                if (freq_delta > max)
                {
                    freq_delta = max;
                }
                df.tune_new -= df.tuning_step * enc_multiplier;
		    }

		    ts.iq_freq_delta = freq_delta;
//	        UiSpectrum_DisplayFilterBW();
		}
		else
		{
            if(pot_diff>0)
            {
                df.tune_new += (df.tuning_step * enc_multiplier);
                //itoa(enc_speed,num,6);
                //UiSpectrumClearDisplay();			// clear display under spectrum scope
                //UiLcdHy28_PrintText(110,156,num,Cyan,Black,0);
            }
            else
            {
                df.tune_new -= (df.tuning_step * enc_multiplier);
            }

            if (enc_multiplier != 1)
            {
                df.tune_new = enc_multiplier*df.tuning_step * div((df.tune_new),enc_multiplier*df.tuning_step).quot;    // keep last digit to zero
            }
		}
		retval = true;
	}
	return retval;
}



//*----------------------------------------------------------------------------
//* Function Name       : UiDriverCheckEncoderOne
//* Object              :
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_CheckEncoderOne(void)
{
	int32_t pot_diff = UiDriverEncoderRead(ENC1);

	if (pot_diff)
	{
		int8_t pot_diff_step = pot_diff < 0?-1:1;
        const BandInfo* band = RadioManagement_GetBand(df.tune_old);

		UiDriver_LcdBlankingStartTimer();	// calculate/process LCD blanking timing
		// Take appropriate action
		switch(ts.enc_one_mode)
		{
		case ENC_ONE_MODE_RTTY_SPEED:
			// Convert to Audio Gain incr/decr
			rtty_ctrl_config.speed_idx = change_and_limit_int(rtty_ctrl_config.speed_idx,pot_diff_step,0,RTTY_SPEED_NUM-1);
			Rtty_Modem_Init(ts.samp_rate);
			UiDriver_DisplayRttySpeed(true);
			break;
			// Update audio volume
		case ENC_ONE_MODE_AUDIO_GAIN:
			ts.rx_gain[RX_AUDIO_SPKR].value = change_and_limit_uint(ts.rx_gain[RX_AUDIO_SPKR].value,pot_diff_step,0,ts.rx_gain[RX_AUDIO_SPKR].max);
			UiDriver_DisplayAfGain(1);
			break;
		case ENC_ONE_MODE_ST_GAIN:
			ts.cw_sidetone_gain = change_and_limit_uint(ts.cw_sidetone_gain,pot_diff_step,0,SIDETONE_MAX_GAIN);

			// we only set a side tone if it would have an effect in the current mode
			// TODO: Should we even disable changes or at least display the box differently if  side tone is not support in currently
			// active mode?
			if (RadioManagement_UsesTxSidetone())
			{
			    Codec_TxSidetoneSetgain(ts.txrx_mode);
			}
			UiDriver_DisplaySidetoneGain(true);
			break;
		case ENC_ONE_MODE_CMP_LEVEL:
			ts.tx_comp_level = change_and_limit_int(ts.tx_comp_level,pot_diff_step,TX_AUDIO_COMPRESSION_MIN,TX_AUDIO_COMPRESSION_MAX);
			AudioManagement_CalcTxCompLevel();		// calculate values for selection compression level
			UiDriver_DisplayCmpLevel(true);	// update on-screen display
			break;
			// Update TX power adj.
		case ENC_ONE_MODE_PWR_GAIN:
		    if (ts.band_index != 17) // not GEN-band
		    {
                ts.pwr_adj[ts.power_level == PA_LEVEL_FULL?ADJ_FULL_POWER:ADJ_REF_PWR][band->band_mode] = change_and_limit_uint(ts.pwr_adj[ts.power_level == PA_LEVEL_FULL?ADJ_FULL_POWER:ADJ_REF_PWR][band->band_mode],pot_diff_step,3,RadioManagement_IsPowerFactorReduce(df.tune_old)?TX_POWER_FACTOR_MAX:TX_POWER_FACTOR_MAX/4);
//				RadioManagement_SetPowerLevel(band, ts.power_level);
		    }
		    else // GEN-band
		    {
		        if((ts.power_level == PA_LEVEL_FULL) && RadioManagement_CBFullPwrEnabled())
		        {
		            ts.power_scale_gen_full = change_and_limit_uint(ts.power_scale_gen_full,pot_diff_step,3,250);
		        }
		        else
		        {
		            ts.power_scale_gen = change_and_limit_uint(ts.power_scale_gen,pot_diff_step,3,200);
		        }
		    }
		    RadioManagement_SetPowerLevel(band, ts.power_level);
			UiDriver_DisplayPWRGain(1);
			break;
		default:
			break;
		}
	}
}
//
//*----------------------------------------------------------------------------
//* Function Name       : UiDriverCheckEncoderTwo
//* Object              :
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_CheckEncoderTwo(void)
{
	int32_t pot_diff = UiDriverEncoderRead(ENC2);

	if (pot_diff != 0)
	{
		UiDriver_LcdBlankingStartTimer();	// calculate/process LCD blanking timing

		if(ts.menu_mode)
		{
			UiMenu_RenderChangeItem(pot_diff);
		}
		else
		{
			int8_t pot_diff_step = pot_diff < 0?-1:1;


			if(ts.txrx_mode == TRX_MODE_RX)
			{

				// dynamic encoder speed , used for notch and peak
				static float    enc_speed_avg = 0.0;  //keeps the averaged encoder speed
				int     delta_t, enc_speed;
				float32_t   enc_multiplier;

				delta_t = ts.audio_int_counter;  // get ticker difference since last enc. change
				ts.audio_int_counter = 0;        //reset tick counter

				if (delta_t > 300)
				{
					enc_speed_avg = 0;    //when leaving speedy turning set avg_speed to 0
				}

				enc_speed = div(4000,delta_t).quot*pot_diff;  // app. 4000 tics per second -> calc. enc. speed.

				if (enc_speed > 500)
				{
					enc_speed = 500;    //limit calculated enc. speed
				}
				if (enc_speed < -500)
				{
					enc_speed = -500;
				}

				enc_speed_avg = 0.1*enc_speed + 0.9*enc_speed_avg; // averaging to smooth encoder speed

				enc_multiplier = 1; //set standard speed

				if (!(ts.expflags1 & EXPFLAGS1_SMOOTH_DYNAMIC_TUNE))        // Smooth dynamic tune is OFF
				{
					if ((enc_speed_avg > 80) || (enc_speed_avg < (-80)))
					{
						enc_multiplier = 10;    // turning medium speed -> increase speed by 10
					}
					if ((enc_speed_avg > 150) || (enc_speed_avg < (-150)))
					{
						enc_multiplier = 30;    //turning fast speed -> increase speed by 100
					}
					if ((enc_speed_avg > 300) || (enc_speed_avg < (-300)))
					{
						enc_multiplier = 100;    //turning fast speed -> increase speed by 100
					}
				}
                else
                {
                    if      ((enc_speed_avg > 350) || (enc_speed_avg < (-350)))
                    {
                        enc_multiplier = 100;    // turning medium speed -> increase speed by 100
                    }
                    else if ((enc_speed_avg > 250) || (enc_speed_avg < (-250)))
                    {
                        enc_multiplier =  50;    //turning fast speed -> increase speed by 50
                    }
                    else if ((enc_speed_avg > 180) || (enc_speed_avg < (-180)))
                    {
                        enc_multiplier =  12;    //turning fast speed -> increase speed by 12
                    }
                    else if ((enc_speed_avg >  90) || (enc_speed_avg < (- 90)))
                    {
                        enc_multiplier =   6;    //turning fast speed -> increase speed by 6
                    }
                    else if ((enc_speed_avg >  45) || (enc_speed_avg < (- 45)))
                    {
                        enc_multiplier =   3;    //turning fast speed -> increase speed by 3
                    }
                    else if ((enc_speed_avg >  30) || (enc_speed_avg < (- 30)))
                    {
                        enc_multiplier =   2;    //turning fast speed -> increase speed by 2
                    }
				}

				// used for notch and peak
				float32_t MAX_FREQ = 5000.0;

				if (ts.filters_p->sample_rate_dec == RX_DECIMATION_RATE_24KHZ)
				{
					MAX_FREQ = 10000.0;
				}
				else if (ts.filters_p->sample_rate_dec == RX_DECIMATION_RATE_12KHZ)
				{
					MAX_FREQ = 5000.0;
				}



				switch(ts.enc_two_mode)
				{
				case ENC_TWO_MODE_RTTY_SHIFT:
					rtty_ctrl_config.shift_idx = change_and_limit_int(rtty_ctrl_config.shift_idx,pot_diff_step,0,RTTY_SHIFT_NUM-1);
					Rtty_Modem_Init(ts.samp_rate);
					UiDriver_DisplayRttyShift(1);
					break;
				case ENC_TWO_MODE_RF_GAIN:
					if(ts.dmod_mode != DEMOD_FM)	 	// is this *NOT* FM?  Change RF gain
					{

							agc_wdsp_conf.thresh = change_and_limit_int(agc_wdsp_conf.thresh,pot_diff_step,-20,120);
							AudioDriver_AgcWdsp_Set();
					}
					else	 		// it is FM - change squelch setting
					{
						ts.fm_sql_threshold = change_and_limit_uint(ts.fm_sql_threshold,pot_diff_step,0,FM_SQUELCH_MAX);
					}

					UiDriver_DisplayRfGain(1);    // change on screen
					break;

					// Update DSP/NB setting
				case ENC_TWO_MODE_SIG_PROC:
						// Signal processor setting
						// this is AGC setting OR noise blanker setting
						if(is_dsp_nb()) // noise blanker is active (ts.nb_setting > 0)
						{
							ts.dsp.nb_setting = change_and_limit_uint(ts.dsp.nb_setting,pot_diff_step,0,MAX_NB_SETTING);
						}
						else // AGC mode setting
						{
							//                    ts.agc_wdsp.tau_decay = change_and_limit_int(ts.agc_wdsp.tau_decay,pot_diff_step * 100,100,5000);
							agc_wdsp_conf.mode = change_and_limit_uint(agc_wdsp_conf.mode,pot_diff_step,0,5);
							agc_wdsp_conf.switch_mode = 1; // set flag, so that mode switching really takes place in AGC_prep
							AudioDriver_AgcWdsp_Set();
						}
					UiDriver_DisplayNoiseBlanker(1);
					break;
				case ENC_TWO_MODE_NR:
					if (is_dsp_nr())        // only allow adjustment if DSP NR is active
					{	//
				    	uint8_t nr_step = DSP_NR_STRENGTH_STEP;
				    	if(ts.dsp.nr_strength >= 190 || ts.dsp.nr_strength <= 10)
				    	{
				    		nr_step = 1;
				    	}
						ts.dsp.nr_strength = change_and_limit_uint(ts.dsp.nr_strength,pot_diff_step * nr_step,DSP_NR_STRENGTH_MIN,DSP_NR_STRENGTH_MAX);
			        	if(ts.dsp.nr_strength == 189)
			        	{
			        		ts.dsp.nr_strength = 185;
			        	}
			        	if(ts.dsp.nr_strength == 11)
			        	{
			        		ts.dsp.nr_strength = 15;
			        	}

						// this causes considerable noise
						//AudioDriver_SetRxAudioProcessing(ts.dmod_mode, false);
						// we do this instead
					    nr_params.alpha = 0.799 + ((float32_t)ts.dsp.nr_strength / 1000.0);
					}
					// Signal processor setting
					UiDriver_DisplayDSPMode(1);
					break;
				case ENC_TWO_MODE_NOTCH_F:
					if (is_dsp_mnotch())   // notch f is only adjustable when notch is enabled
					{
						if(pot_diff < 0)
						{
							ts.dsp.notch_frequency = ts.dsp.notch_frequency - 5.0 * enc_multiplier;
						}
						if(pot_diff > 0)
						{
							ts.dsp.notch_frequency = ts.dsp.notch_frequency + 5.0 * enc_multiplier;
						}

						if(ts.dsp.notch_frequency > MAX_FREQ)
						{
							ts.dsp.notch_frequency = MAX_FREQ;
						}
						if(ts.dsp.notch_frequency < MIN_PEAK_NOTCH_FREQ)
						{
							ts.dsp.notch_frequency = MIN_PEAK_NOTCH_FREQ;
						}
						// display notch frequency
						// set notch filter instance
						AudioDriver_SetProcessingChain(ts.dmod_mode, false);
						UiDriver_DisplayDSPMode(1);
					}
					break;
				case ENC_TWO_MODE_BASS_GAIN:
					ts.dsp.bass_gain = change_and_limit_int(ts.dsp.bass_gain,pot_diff_step,MIN_BASS,MAX_BASS);
//					ts.dsp.tx_eq_gain[0] = change_and_limit_int(ts.dsp.tx_eq_gain[0],pot_diff_step,MIN_TX_EQ,MAX_TX_EQ);
					// set filter instance
					AudioDriver_SetProcessingChain(ts.dmod_mode, false);
					// display bass gain
					UiDriver_DisplayTone(true);
					break;
				case ENC_TWO_MODE_TREBLE_GAIN:
					ts.dsp.treble_gain = change_and_limit_int(ts.dsp.treble_gain,pot_diff_step,MIN_TREBLE,MAX_TREBLE);
//					ts.dsp.tx_eq_gain[4] = change_and_limit_int(ts.dsp.tx_eq_gain[4],pot_diff_step,MIN_TX_EQ,MAX_TX_EQ);
					// set filter instance
					AudioDriver_SetProcessingChain(ts.dmod_mode, false);
					// display treble gain
					UiDriver_DisplayTone(true);
					break;

				case ENC_TWO_MODE_PEAK_F:
					if (is_dsp_mpeak())   // peak f is only adjustable when peak is enabled
					{
						if(pot_diff < 0)
						{
							ts.dsp.peak_frequency = ts.dsp.peak_frequency - 5.0 * enc_multiplier;
						}
						if(pot_diff > 0)
						{
							ts.dsp.peak_frequency = ts.dsp.peak_frequency + 5.0 * enc_multiplier;
						}
						if(ts.dsp.peak_frequency > MAX_FREQ)
						{
							ts.dsp.peak_frequency = MAX_FREQ;
						}
						if(ts.dsp.peak_frequency < MIN_PEAK_NOTCH_FREQ)
						{
							ts.dsp.peak_frequency = MIN_PEAK_NOTCH_FREQ;
						}
						// set notch filter instance
						AudioDriver_SetProcessingChain(ts.dmod_mode, false);
						// display peak frequency
						UiDriver_DisplayDSPMode(1);
					}
					break;
				default:
					break;
				}
			}
			else { // in TX case only bass & treble gain can be adjusted with encoder TWO

				// Take appropriate action
				switch(ts.enc_two_mode)
				{
				case ENC_TWO_MODE_BASS_GAIN:
//					ts.dsp.tx_bass_gain = change_and_limit_int(ts.dsp.tx_bass_gain,pot_diff_step,MIN_TX_BASS,MAX_TX_BASS);
                    ts.dsp.tx_eq_gain[0] = change_and_limit_int(ts.dsp.tx_eq_gain[0],pot_diff_step,MIN_TX_EQ,MAX_TX_EQ);
					// set filter instance
					AudioDriver_SetProcessingChain(ts.dmod_mode, false);
					// display bass gain
					UiDriver_DisplayTone(true);
					break;
				case ENC_TWO_MODE_TREBLE_GAIN:
//					ts.dsp.tx_treble_gain = change_and_limit_int(ts.dsp.tx_treble_gain,pot_diff_step,MIN_TX_TREBLE,MAX_TX_TREBLE);
                    ts.dsp.tx_eq_gain[4] = change_and_limit_int(ts.dsp.tx_eq_gain[4],pot_diff_step,MIN_TX_EQ,MAX_TX_EQ);
					// set filter instance
					AudioDriver_SetProcessingChain(ts.dmod_mode, false);
					// display treble gain
					UiDriver_DisplayTone(true);
					break;
				default:
					break;
				}
			}
		}
	}
}

//
//*----------------------------------------------------------------------------
//* Function Name       : UiDriverCheckEncoderThree
//* Object              :
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_CheckEncoderThree(void)
{
	int 	pot_diff;

	pot_diff = UiDriverEncoderRead(ENC3);


	if (pot_diff)
	{
		int8_t pot_diff_step = pot_diff < 0?-1:1;

		UiDriver_LcdBlankingStartTimer();	// calculate/process LCD blanking timing
		if (filter_path_change)
		{
			AudioFilter_NextApplicableFilterPath(PATH_ALL_APPLICABLE | (pot_diff < 0?PATH_DOWN:PATH_UP),AudioFilter_GetFilterModeFromDemodMode(ts.dmod_mode),ts.filter_path);
			// we store the new filter in the current active filter location
			AudioDriver_SetProcessingChain(ts.dmod_mode, false);
			// we activate it (in fact the last used one, which is the newly selected one);

			UiDriver_UpdateDisplayAfterParamChange();
		}
		else  if(ts.menu_mode)
		{
			UiMenu_RenderChangeItemValue(pot_diff);
		}
		else
		{
			// Take appropriate action
			switch(ts.enc_thr_mode)
			{
			// Update RIT value
			case ENC_THREE_MODE_RIT:
				if(ts.txrx_mode == TRX_MODE_RX)
				{
					int16_t old_rit_value = ts.rit_value;
					ts.rit_value = change_and_limit_int(ts.rit_value,pot_diff_step,MIN_RIT_VALUE,MAX_RIT_VALUE);

					ts.dial_moved = ts.rit_value != old_rit_value;

					// Update RIT
					UiDriver_DisplayRit(1);
					// Change frequency
					UiDriver_FrequencyUpdateLOandDisplay(false);
				}
				break;
				// Keyer speed
			case ENC_THREE_MODE_CW_SPEED:
				// Convert to Audio Gain incr/decr
				ts.cw_keyer_speed = change_and_limit_int(ts.cw_keyer_speed,pot_diff_step,CW_KEYER_SPEED_MIN,CW_KEYER_SPEED_MAX);
				CwGen_SetSpeed();
				ts.cw_keyer_speed_bak = ts.cw_keyer_speed;
				UiDriver_DisplayKeyerSpeed(1);
				break;
			case ENC_THREE_MODE_PSK_SPEED:
				psk_ctrl_config.speed_idx = change_and_limit_int(psk_ctrl_config.speed_idx,pot_diff_step,0,PSK_SPEED_NUM-1);
				UiDriver_TextMsgClear();
				Psk_Modem_Init(ts.samp_rate);
				UiDriver_DisplayPskSpeed(true);
				break;
				// Update audio volume
			case ENC_THREE_MODE_INPUT_CTRL:
				// in voice mode, adjust audio input gain
			{
				uint16_t gain_max = ts.tx_audio_source == TX_AUDIO_MIC?MIC_GAIN_MAX:LINE_GAIN_MAX;
				uint16_t gain_min = ts.tx_audio_source == TX_AUDIO_MIC?MIC_GAIN_MIN:LINE_GAIN_MIN;

				ts.tx_gain[ts.tx_audio_source] = change_and_limit_int(ts.tx_gain[ts.tx_audio_source],pot_diff_step,gain_min,gain_max);

				if (ts.tx_audio_source == TX_AUDIO_MIC)
				{
					Codec_SwitchMicTxRxMode(ts.txrx_mode);
				}
				UiDriver_DisplayLineInModeAndGain(1);
			}
			break;
			default:
				break;
			}
		}
	}
}

static bool UiDriver_IsApplicableEncoderOneMode(uint8_t mode)
{
	bool retval = true;
	switch(mode)
	{
	case ENC_ONE_MODE_AUDIO_GAIN:
		retval = !(ts.txrx_mode == TRX_MODE_TX);
		break;
	case ENC_ONE_MODE_RTTY_SPEED:
		// only switch to rtty adjustment, if rtty enabled!
		retval = is_demod_rtty();
		break;
	case ENC_ONE_MODE_ST_GAIN:
		retval = RadioManagement_UsesTxSidetone();
		break;
	case ENC_ONE_MODE_CMP_LEVEL:
		retval = ts.dmod_mode != DEMOD_CW && ts.dmod_mode != DEMOD_DIGI;
		break;
	case ENC_ONE_MODE_PWR_GAIN:
		retval = (ts.txrx_mode == TRX_MODE_TX);
		break;
	}
	return retval;
}


static void UiDriver_DisplayEncoderOneMode()
{
	// upper box
	if(ts.txrx_mode != TRX_MODE_TX)
	{
		UiDriver_DisplayAfGain(ts.enc_one_mode == ENC_ONE_MODE_AUDIO_GAIN);
	}
	else
	{
		UiDriver_DisplayPWRGain(ts.enc_one_mode == ENC_ONE_MODE_PWR_GAIN);
	}

	// lower box
	switch(ts.enc_one_mode)
	{
	case ENC_ONE_MODE_RTTY_SPEED:
		UiDriver_DisplayRttySpeed(1);
		break;
	case ENC_ONE_MODE_ST_GAIN:
		UiDriver_DisplaySidetoneGain(1);
		break;
	case ENC_ONE_MODE_CMP_LEVEL:
		UiDriver_DisplayCmpLevel(1);
		break;
	default:
		// what to display if lower box is not active
        if ( RadioManagement_UsesTxSidetone())
        {
            UiDriver_DisplaySidetoneGain( false );
            if ( is_demod_rtty())
            {
                UiDriver_DisplayRttySpeed( false );
            }
        }
        else
        {
            UiDriver_DisplayCmpLevel( false );
        }
	}
}

static bool UiDriver_IsApplicableEncoderTwoMode(uint8_t mode)
{
	bool retval = true;
	switch(mode)
	{
	case ENC_TWO_MODE_RTTY_SHIFT:
		// only switch to rtty adjustment, if rtty enabled!
		retval = is_demod_rtty();
		break;
	case ENC_TWO_MODE_NOTCH_F:
		retval = is_dsp_mnotch();
		break;
	case ENC_TWO_MODE_PEAK_F:
		retval = is_dsp_mpeak();
		break;
	}
	return retval;
}


static void UiDriver_DisplayEncoderTwoMode()
{

	uint8_t inactive = ts.menu_mode?0:1;
	// we use this to disable all active displays once in menu mode
	switch(ts.enc_two_mode)
	{
	case ENC_TWO_MODE_RF_GAIN:
		UiDriver_DisplayRfGain(inactive);
		UiDriver_DisplayNoiseBlanker(0);
		UiDriver_DisplayDSPMode(0);
		break;
	case ENC_TWO_MODE_SIG_PROC:
		UiDriver_DisplayRfGain(0);
		//		UiDriver_DisplayNoiseBlanker(inactive);
		UiDriver_DisplayNoiseBlanker(1);
		UiDriver_DisplayDSPMode(0);
		break;
	case ENC_TWO_MODE_PEAK_F:
	case ENC_TWO_MODE_NOTCH_F:
	case ENC_TWO_MODE_NR:
		UiDriver_DisplayRfGain(0);
		UiDriver_DisplayNoiseBlanker(0);
		UiDriver_DisplayDSPMode(inactive);
		break;
	case ENC_TWO_MODE_BASS_GAIN:
		UiDriver_DisplayDSPMode(0);
		UiDriver_DisplayTone(inactive);
		break;
	case ENC_TWO_MODE_TREBLE_GAIN:
		UiDriver_DisplayDSPMode(0);
		UiDriver_DisplayTone(inactive);
		break;
	case ENC_TWO_MODE_RTTY_SHIFT:
		UiDriver_DisplayRfGain(0);
		UiDriver_DisplayDSPMode(0);
		UiDriver_DisplayRttyShift(1);
		break;
	default:
		UiDriver_DisplayRfGain(0);
		UiDriver_DisplayNoiseBlanker(0);
		UiDriver_DisplayDSPMode(0);
		break;
	}

}


static bool UiDriver_IsApplicableEncoderThreeMode(uint8_t mode)
{
	bool retval = true;
	switch(mode)
	{
	case ENC_THREE_MODE_CW_SPEED:
		retval = ts.dmod_mode == DEMOD_CW;
		break;
	case ENC_THREE_MODE_PSK_SPEED:
		retval = is_demod_psk();
		break;
	case ENC_THREE_MODE_INPUT_CTRL:
		retval = ts.dmod_mode != DEMOD_DIGI || ts.digital_mode != DigitalMode_BPSK;
		//retval = ts.dmod_mode != DEMOD_DIGI || (ts.digital_mode != DigitalMode_BPSK && ts.digital_mode != DigitalMode_RTTY);
		break;
	}
	return retval;
}


static void UiDriver_DisplayEncoderThreeMode()
{
	// upper box
	UiDriver_DisplayRit(ts.enc_thr_mode == ENC_THREE_MODE_RIT);

	// lower box
	switch(ts.enc_thr_mode)
	{
	case ENC_THREE_MODE_CW_SPEED:
		UiDriver_DisplayKeyerSpeed(1);
		break;
	case ENC_THREE_MODE_PSK_SPEED:
		UiDriver_DisplayPskSpeed(1);
		break;
	case ENC_THREE_MODE_INPUT_CTRL:
		UiDriver_DisplayLineInModeAndGain(1);
		break;
	default:
		// this defines what is shown if the lower box is not actively selected
		if (ts.dmod_mode == DEMOD_CW)
		{
			UiDriver_DisplayKeyerSpeed(0);
		}
		else if (ts.dmod_mode == DEMOD_DIGI && ts.digital_mode == DigitalMode_BPSK)
		{
			UiDriver_DisplayPskSpeed(0);
		}
		else
		{
			UiDriver_DisplayLineInModeAndGain(0);
		}
		break;

	}
}


/**
 * Handles the execution of the change encoder logic for the 3 encoders
 */

static void UiDriver_ChangeEncoderMode(volatile uint8_t* mode_ptr, uint8_t num_modes, bool (*is_applicable_f)(uint8_t), void(*display_encoder_f)())
{
	if(ts.menu_mode == false)   // changes only when not in menu mode
	{
		uint8_t new_enc_mode = *mode_ptr;
		do
		{
			new_enc_mode++;
			new_enc_mode %= num_modes;
		} while ((*is_applicable_f)(new_enc_mode)  == false && new_enc_mode != *mode_ptr );
		if (new_enc_mode != *mode_ptr)
		{
			*mode_ptr = new_enc_mode;
			(*display_encoder_f)();
		}
	}
}

static void UiDriver_ChangeEncoderOneMode(void)
{
	UiDriver_ChangeEncoderMode(&ts.enc_one_mode, ENC_ONE_NUM_MODES, UiDriver_IsApplicableEncoderOneMode, UiDriver_DisplayEncoderOneMode);
}

static void UiDriver_ChangeEncoderTwoMode(void)
{
	UiDriver_ChangeEncoderMode(&ts.enc_two_mode, ENC_TWO_NUM_MODES, UiDriver_IsApplicableEncoderTwoMode, UiDriver_DisplayEncoderTwoMode);
}

static void UiDriver_ChangeEncoderThreeMode(void)
{
	UiDriver_ChangeEncoderMode(&ts.enc_thr_mode, ENC_THREE_NUM_MODES, UiDriver_IsApplicableEncoderThreeMode, UiDriver_DisplayEncoderThreeMode);
}

/**
 * @brief Displays audio speaker volume
 */
static void UiDriver_DisplayAfGain(bool encoder_active)
{
	UiDriver_EncoderDisplaySimple(0,0,"AFG", encoder_active, ts.rx_gain[RX_AUDIO_SPKR].value);
}

/**
 * @brief Displays power volume
 */
static void UiDriver_DisplayPWRGain(bool encoder_active)
{
	if (ts.band_index != 17) // not GEN-band
	{
		const BandInfo* band = RadioManagement_GetBand(df.tune_old);
		UiDriver_EncoderDisplaySimple(0,0,"PWR", encoder_active, ts.pwr_adj[ts.power_level == PA_LEVEL_FULL?ADJ_FULL_POWER:ADJ_REF_PWR][band->band_mode]);
	}
	else // GEN-band
	{
		UiDriver_EncoderDisplaySimple(0,0,"PWR", encoder_active, ((ts.power_level == PA_LEVEL_FULL && RadioManagement_CBFullPwrEnabled())? ts.power_scale_gen_full : ts.power_scale_gen));
	}
}

/**
 * @brief Display CW Sidetone gain (used during CW TX or training)
 */
static void UiDriver_DisplaySidetoneGain(bool encoder_active)
{
	UiDriver_EncoderDisplaySimple(1,0,"STG", encoder_active, ts.cw_sidetone_gain);
}

/**
 * @brief Display TX Compressor Level
 */
static void UiDriver_DisplayCmpLevel(bool encoder_active)
{
	ushort 	color = encoder_active?White:Grey;
	char	temp[5];
	const char* outs;

	if (ts.tx_comp_level == TX_AUDIO_COMPRESSION_MIN)
	{
		outs ="OFF";
	}
	else if(ts.tx_comp_level < TX_AUDIO_COMPRESSION_MAX)	 	// 	display numbers for all but the highest value
	{
		snprintf(temp,5," %02d",ts.tx_comp_level);
		outs = temp;
	}
	else
	{
		color = Yellow; // Custom value - use yellow
		outs ="CUS";
	}

	UiDriver_EncoderDisplay(1,0,"CMP" , encoder_active, outs, color);
}

uint32_t UiDriver_GetActiveDSPFunctions()
{
	return ts.dsp.active & (DSP_NOTCH_ENABLE|DSP_NR_ENABLE|DSP_ANR_ENABLE|DSP_MNOTCH_ENABLE|DSP_MPEAK_ENABLE);
}

static void UiDriver_DisplayDSPMode(bool encoder_active)
{
//    if(ts.show_wide_spectrum)
//    {
//        return;
//    }
	uint32_t clr = White;
	uint32_t clr_val = White;

	char val_txt[7] = { 0x0 };
	bool txt_is_value = false;
	const char* txt[2] = { "DSP", NULL };

	//uint32_t dsp_functions_active = ts.dsp.active & (DSP_NOTCH_ENABLE|DSP_NR_ENABLE|DSP_MNOTCH_ENABLE|DSP_MPEAK_ENABLE);
	uint32_t dsp_functions_active =UiDriver_GetActiveDSPFunctions();

	UiVk_Redraw();			//virtual keypads call (refresh purpose)

	switch (dsp_functions_active)
	{
	case 0: // all off
		clr = Grey2;
		txt[1] = "OFF";
		break;
	case DSP_NR_ENABLE:
		txt[0] = "NR";
		snprintf(val_txt,7,"%5u", ts.dsp.nr_strength);
		txt[1] = val_txt;
		txt_is_value = true;
		break;
	case DSP_NOTCH_ENABLE:
		txt[1] = "A-NOTCH";
		break;
	case DSP_NOTCH_ENABLE|DSP_NR_ENABLE:
        txt[0] = "NR+NOTC";
        snprintf(val_txt,7,"%5u", ts.dsp.nr_strength);
        txt[1] = val_txt;
        txt_is_value = true;
        break;
	case DSP_MNOTCH_ENABLE:
		txt[0] = "M-NOTCH";
		snprintf(val_txt,7,"%5lu", ts.dsp.notch_frequency);
		txt[1] = val_txt;
		txt_is_value = true;
		break;
	case DSP_MPEAK_ENABLE:
		txt[0] = "PEAK";
		snprintf(val_txt,7,"%5lu", ts.dsp.peak_frequency);
		txt[1] = val_txt;
		txt_is_value = true;
		break;
    case DSP_ANR_ENABLE:
        txt[1] = "ANR";
        break;
	default: // unsupported combination of DSP functions yield error display now
		clr = Grey2;
		txt[1] = "ERROR";
		break;
	}

	UiDriver_LeftBoxDisplay(0,txt[0],encoder_active,txt[1],clr,clr_val,txt_is_value);
}


static void UiDriver_DisplayKeyerSpeed(bool encoder_active)
{
	uint16_t 	color = encoder_active?White:Grey;
	const char* txt;
	char  txt_buf[5];

	txt = "WPM";
	snprintf(txt_buf,5,"%3d",ts.cw_keyer_speed);

	UiDriver_EncoderDisplay(1,2,txt, encoder_active, txt_buf, color);
}

static void UiDriver_DisplayRttySpeed(bool encoder_active)
{
	uint16_t  color = encoder_active?White:Grey;
	UiDriver_EncoderDisplay(1,0,"BD", encoder_active, rtty_speeds[rtty_ctrl_config.speed_idx].label, color);
}

static void UiDriver_DisplayPskSpeed(bool encoder_active)
{
	uint16_t  color = encoder_active?White:Grey;
	UiDriver_EncoderDisplay(1,2,"PSK", encoder_active, psk_speeds[psk_ctrl_config.speed_idx].label, color);
}

static void UiDriver_DisplayRttyShift(bool encoder_active)
{
	uint16_t  color = encoder_active?White:Grey;
	UiDriver_EncoderDisplay(1,1,"SFT", encoder_active, rtty_shifts[rtty_ctrl_config.shift_idx].label, color);
}


static void UiDriver_DisplayLineInModeAndGain(bool encoder_active)
{
	ushort 	color = encoder_active?White:Grey;
	const char* txt;
	char  txt_buf[5];

	bool gain_external_control = false;
	// if true, gain is controlled externally and ENC3 encoder does not do anything.

	switch (ts.tx_audio_source)
	{
	case TX_AUDIO_MIC:
		txt = "MIC";
		break;
	case TX_AUDIO_LINEIN_L:										// Line gain
		txt = "L>L";
		break;
	case TX_AUDIO_LINEIN_R:										// Line gain
		txt = "L>R";
		break;
	case TX_AUDIO_DIG:										// Line gain
		txt = "DIG";
		gain_external_control = true;
		break;
	case TX_AUDIO_DIGIQ:
		txt = "DIQ";
		gain_external_control = true;
		break;
	default:
		txt = "???";
	}

	if (gain_external_control == true)
	{
		snprintf(txt_buf,5,"EXT");
	}
	else
	{
		snprintf(txt_buf,5,"%3d",ts.tx_gain[ts.tx_audio_source]);
	}

	UiDriver_EncoderDisplay(1,2,txt, encoder_active, txt_buf, color);
}

static void UiDriver_DisplayRfGain(bool encoder_active)
{
	uint32_t color = encoder_active?White:Grey;

	char	temp[5];
	const char* label = "???";
	int32_t value;
	if(ts.dmod_mode != DEMOD_FM) // NOT FM
	{
		label = "AGC";
		value = agc_wdsp_conf.thresh;
	}
	else // use SQL for FM
	{
		label = "SQL";
		value = ts.fm_sql_threshold;
	}
	snprintf(temp,5," %02ld",value);

	UiDriver_EncoderDisplay(0,1,label, encoder_active, temp, color);

}

uint32_t UiDriver_GetNBColor()
{
    uint32_t retval;

    if(ts.dsp.nb_setting >= NB_WARNING3_SETTING)
        retval = Red;        // above this value, make it red
    else if(ts.dsp.nb_setting >= NB_WARNING2_SETTING)
        retval = Orange;     // above this value, make it orange
    else if(ts.dsp.nb_setting >= NB_WARNING1_SETTING)
        retval = Yellow;     // above this value, make it yellow
    else
        retval = White;      // Otherwise, make it white

    return retval;
}
//*----------------------------------------------------------------------------
//* Function Name       : UiDriverChangeSigProc
//* Object              : Display settings related to signal processing - DSP NR or Noise Blanker strength
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_DisplayNoiseBlanker(bool encoder_active)
{
//    if(ts.show_wide_spectrum)
//    {
//        return;
//    }
	uint32_t 	color = encoder_active?White:Grey;
	char	temp[5];
	const char *label, *val_txt;
	int32_t value = 0;
//	bool is_active = false;
	//label = "NB";
	//    label = "DEC";
//#if 0
		//
		// Noise blanker settings display
		//
		if(is_dsp_nb())	 	// is noise blanker to be displayed
		{
			if(encoder_active)
			{
			    color = UiDriver_GetNBColor();
			}
			label = "NB";
			value = ts.dsp.nb_setting;
//			snprintf(temp,5,"%3ld",value);
//			val_txt = temp;
			if(value)
			{
				snprintf(temp,5,"%3ld",value);
				val_txt = temp;
			}
			else
			{
				val_txt = "OFF";
			}
		}

		else
		{
//#endif
			switch(agc_wdsp_conf.mode)
			{
			case 0:
				label = "vLO";
				break;
			case 1:
				label = "LON";
				break;
			case 2:
				label = "SLO";
				break;
			case 3:
				label = "MED";
				break;
			case 4:
				label = "FAS";
				break;
			case 5:
				label = "OFF";
				break;
			default:
				label = "???";
				break;
			}
			value = (int32_t)(agc_wdsp_conf.tau_decay[agc_wdsp_conf.mode] / 10.0);
			snprintf(temp,5,"%3ld",value);
			val_txt = temp;
		}
		UiDriver_EncoderDisplay(1,1,label, encoder_active, val_txt, color);
}

#define NOTCH_DELTA_Y (2*ENC_ROW_H)

//*----------------------------------------------------------------------------
//* Function Name       : UiDriverDisplayBass
//* Object              : Display settings related to bass & treble filter
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_DisplayTone(bool encoder_active)
{

	// UiLcdHy28_DrawFullRect(POS_AG_IND_X, POS_AG_IND_Y + NOTCH_DELTA_Y, 16 + 12 , 112, Black);

	bool enable = (ts.enc_two_mode == ENC_TWO_MODE_BASS_GAIN);
	char temp[5];
	int bas,tre;

//	if(ts.txrx_mode == TRX_MODE_TX) // if in TX_mode, display TX bass gain instead of RX_bass gain!
	if(ts.txrx_mode == TRX_MODE_TX) // if in TX_mode, display TX EQ gain instead of RX_bass gain!
	{
//		bas = ts.dsp.tx_bass_gain;
//		tre = ts.dsp.tx_treble_gain;
		bas = ts.dsp.tx_eq_gain[0];
		tre = ts.dsp.tx_eq_gain[4];
	}
	else
	{
		bas = ts.dsp.bass_gain;
		tre = ts.dsp.treble_gain;
	}

	snprintf(temp,5,"%3d", bas);

	// use 2,1 for placement below existing boxes
	UiDriver_EncoderDisplay(0,1,"BAS", enable && encoder_active, temp, White);


	enable = (ts.enc_two_mode == ENC_TWO_MODE_TREBLE_GAIN);

	snprintf(temp,5,"%3d", tre);

	// use 2,2 for placement below existing boxes
	UiDriver_EncoderDisplay(1,1,"TRB", enable && encoder_active, temp, White);

} // end void UiDriverDisplayBass

//
//*----------------------------------------------------------------------------
//* Function Name       : UiDriverChangeRit
//* Object              :
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_DisplayRit(bool encoder_active)
{
	char	temp[5];

	uint32_t color = ts.rit_value? Green : (encoder_active ? White:Grey);

	snprintf(temp,5,ts.rit_value?"%+3i":"%3i", ts.rit_value);

	UiDriver_EncoderDisplay(0,2,"RIT", encoder_active, temp, color);
}

static void UiDriver_DisplayModulationType()
{

//	ushort bgclr = ts.dvmode?Orange:Blue;
	ushort bgclr = ts.dvmode?Orange:sd.boxes_colour;
	ushort color = digimodes[ts.digital_mode].enabled?(ts.dvmode?Black:White):Grey2;
	char txt_empty[]="       ";
//	char txt_SSB[]="SSB";
	char txt_CW[]="CW";


	//const char* txt = digimodes[ts.digital_mode].label;
	const char* txt;
	switch(ts.dmod_mode)
	{
	case DEMOD_DIGI:
#if defined(USE_FREEDV)
	    if  (ts.digital_mode == DigitalMode_FreeDV)
	    {
	        txt = freedv_modes[freedv_conf.mode].label;
	    }
	    else
#endif
	    {
	        txt = digimodes[ts.digital_mode].label;
	    }
		break;
#if !defined(SDR_AMBER_480_320) && !defined(OVI40_MOD_480_320)
    case DEMOD_LSB:
        txt = ts.show_wide_spectrum?"LSB":"SSB";
        break;
	case DEMOD_USB:
//		txt = txt_SSB;
		txt = ts.show_wide_spectrum?"USB":"SSB";
		break;
	case DEMOD_CW:
		txt = txt_CW;
		break;
	case DEMOD_AM:
	    txt = ts.show_wide_spectrum?"AM":" ";
		break;
	case DEMOD_SAM:
	    txt = ts.show_wide_spectrum?"SAM":" ";
		break;
	case DEMOD_FM:
	    txt = ts.show_wide_spectrum?"FM":" ";
		break;
#else
    case DEMOD_LSB:
        txt = "LSB";
        break;
	case DEMOD_USB:
		txt = "USB";
		break;
	case DEMOD_CW:
		txt = txt_CW;
		break;
	case DEMOD_AM:
	    txt = "AM";
		break;
	case DEMOD_SAM:
	    txt = "SAM";
		break;
	case DEMOD_FM:
	    txt = "FM";
		break;
#endif
	default:
		txt = txt_empty;
	}

//    if(ts.show_wide_spectrum)
//    {
//        return;
//    }

	// Draw line for box
//	UiLcdHy28_DrawStraightLine(ts.Layout->DIGMODE.x,(ts.Layout->DIGMODE.y - 1),ts.Layout->DIGMODE.w,LCD_DIR_HORIZONTAL,bgclr);
	UiLcdHy28_PrintTextCentered(ts.Layout->DIGMODE.x,ts.Layout->DIGMODE.y,ts.Layout->DIGMODE.w,txt,color,bgclr,0);
#if !defined(SDR_AMBER_480_320) && !defined(OVI40_MOD_480_320)
	if(disp_resolution!=RESOLUTION_320_240)
	{
		UiLcdHy28_DrawStraightLineDouble(ts.Layout->DIGMODE.x,ts.Layout->DIGMODE.y+12,ts.Layout->DIGMODE.w,LCD_DIR_HORIZONTAL,bgclr);
//		UiLcdHy28_DrawStraightLine(ts.Layout->DIGMODE.x,ts.Layout->DIGMODE.y+14,ts.Layout->DIGMODE.w,LCD_DIR_HORIZONTAL,Blue);
		UiLcdHy28_DrawStraightLine(ts.Layout->DIGMODE.x,ts.Layout->DIGMODE.y+14,ts.Layout->DIGMODE.w,LCD_DIR_HORIZONTAL,sd.boxes_colour);
	}
#endif
	//fdv_clear_display();
}
/**
 * Converts a power value in mW into useful null-terminated string
 * @param txt char array of at least 5 characters
 * @param txt_len length of txt array
 * @param power_mW power value, if 0 result is "FULL"
 */
void UiDriver_Power2String(char* txt, size_t txt_len,uint32_t power_mW)
{
    if (power_mW == 0 )
    {
        snprintf(txt,txt_len,"FULL");
    }
    else if (power_mW < 100)
    {
        snprintf(txt,txt_len,"%ldmW",power_mW);
    }
    else if (power_mW < 1000)
    {
        snprintf(txt,txt_len,"0.%ldW",power_mW/100);
    }
    else
    {
        snprintf(txt,txt_len,"%ldW",power_mW/1000);
    }
}

static void UiDriver_DisplayPowerLevel()
{
    uint16_t fg_clr = White;
    char txt[5];

    UiDriver_Power2String(txt,sizeof(txt),ts.power);

//    uint16_t bg_clr = Blue; // normal operation
    uint16_t bg_clr = sd.boxes_colour; // normal operation

    if (RadioManagement_IsTxDisabled() == true)
    {
        // we'll not transmit, power is irrelevant
        bg_clr = Grey;
        fg_clr = Grey4;
    }
    else if (ts.tx_power_factor == 0)
    {
        // no output at all will be generate with power factor 0
        // probably not calibrated PA
        bg_clr = Red;
    }
    else if (ts.power_modified == true)
    {
        // transmit p, power is irrelevant
        bg_clr = Orange;
        fg_clr = Grey4;
    }

	UiLcdHy28_PrintTextCentered((ts.Layout->PW_IND.x),(ts.Layout->PW_IND.y),ts.Layout->PW_IND.w,txt,fg_clr,bg_clr,0);
}

static void UiDriver_DisplayDbm(void)
{
    // TODO: Move to UI Driver
    bool display_something = false;
    static long oldVal=99999;
    static uint8_t dBmShown=0;
    if( ts.txrx_mode == TRX_MODE_RX)
    {
        long val;
        const char* unit_label;

        switch(ts.display_dbm)
        {
        case DISPLAY_S_METER_DBM:
            display_something = true;
            val = sm.dbm;
            unit_label = "dBm   ";

            break;
        case DISPLAY_S_METER_DBMHZ:
            display_something = true;
            val = sm.dbmhz;
            unit_label = "dBm/Hz";
            break;
        }

        if ((display_something == true) && (val!=oldVal))
        {
        	if(!ts.show_wide_spectrum)
        	{
				char txt[12];
				snprintf(txt,12,"%4ld      ", val);
//				UiLcdHy28_PrintText(ts.Layout->DisplayDbm.x,ts.Layout->DisplayDbm.y,txt,White,Blue,0);
//				UiLcdHy28_PrintText(ts.Layout->DisplayDbm.x+SMALL_FONT_WIDTH * 4,ts.Layout->DisplayDbm.y,unit_label,White,Blue,4);
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
                UiLcdHy28_PrintText(ts.Layout->DisplayDbm.x,ts.Layout->DisplayDbm.y,txt,White,sd.boxes_colour,0);
                UiLcdHy28_PrintText(ts.Layout->DisplayDbm.x+SMALL_FONT_WIDTH * 4,ts.Layout->DisplayDbm.y,unit_label,White,sd.boxes_colour,4);
	#else
                UiLcdHy28_PrintText(ts.Layout->DisplayDbm.x,ts.Layout->DisplayDbm.y,txt,White,Black,0);
                UiLcdHy28_PrintText(ts.Layout->DisplayDbm.x+SMALL_FONT_WIDTH * 4,ts.Layout->DisplayDbm.y,unit_label,White,Black,4);
	#endif
#else
                UiLcdHy28_PrintText(ts.Layout->DisplayDbm.x,ts.Layout->DisplayDbm.y,txt,White,Black,0);
                UiLcdHy28_PrintText(ts.Layout->DisplayDbm.x+SMALL_FONT_WIDTH * 4,ts.Layout->DisplayDbm.y,unit_label,White,Black,4);
#endif
        	}
        	else
        	{
				char txt[7];
				snprintf(txt,12,"%4ld ", val);
				UiLcdHy28_PrintText(ts.Layout->DisplayDbm.x,ts.Layout->DisplayDbm.y,txt,White,Black,0);
				UiLcdHy28_PrintText(ts.Layout->DisplayDbm.x,ts.Layout->DisplayDbm.y+13,unit_label,White,Black,4);

        	}
            oldVal=val;     //this will prevent from useless redrawing the same
            dBmShown=1;     //for indicate that dms are shown and erase function may it clear when needed
        }
    }

    // clear the display since we are not showing dBm or dBm/Hz or we are in TX mode
    if ((display_something == false) && (dBmShown==1))
    {
    	if(!ts.show_wide_spectrum)
    	{
    		UiLcdHy28_DrawFullRect(ts.Layout->DisplayDbm.x, ts.Layout->DisplayDbm.y, 15, SMALL_FONT_WIDTH * 10 , Black);
    	}
    	else
    	{
    		UiLcdHy28_DrawFullRect(ts.Layout->DisplayDbm.x, ts.Layout->DisplayDbm.y, 22, 43 , Black);
    	}
        dBmShown=0;     //just to indicate that dbm is erased
        oldVal=99999;   //some value that will enforce refresh when user enable display dbm
    }
}

/**
 * Handles calculation of SMeter values and also handles the input gain adjustment to keep input inside good range
 */
void RadioManagement_HandleIqGainAndSMeter()
{
    if(ts.txrx_mode == TRX_MODE_RX)
    {
        RadioManagement_HandleRxIQSignalCodecGain();

        // lowpass IIR filter
        // Wheatley 2011: two averagers with two time constants
        // IIR filter with one element analog to 1st order RC filter
        // but uses two different time constants (ALPHA = 1 - e^(-T/Tau)) depending on
        // whether the signal is increasing (attack) or decreasing (decay)
        // we scale Alpha values by 100 so that we have a usable range for configuring the speed of attack and decay in menu items (from 1 to 100)

        const float32_t alpha_scale_inv = 0.01;
        float32_t attackAlpha = sm.config.alphaSplit.AttackAlpha *alpha_scale_inv;

        float32_t decayAlpha = sm.config.alphaSplit.DecayAlpha *alpha_scale_inv;
        sm.AttackAvedbm =   (1.0 - attackAlpha) * sm.AttackAvedbm   + attackAlpha * sm.dbm_cur;
        sm.DecayAvedbm =    (1.0 - decayAlpha)  * sm.DecayAvedbm    + decayAlpha  * sm.dbm_cur;
        sm.AttackAvedbmhz = (1.0 - attackAlpha) * sm.AttackAvedbmhz + attackAlpha * sm.dbmhz_cur;
        sm.DecayAvedbmhz =  (1.0 - decayAlpha)  * sm.DecayAvedbmhz  + decayAlpha  * sm.dbmhz_cur;

        if (sm.AttackAvedbm > sm.DecayAvedbm)
        { // if attack average is larger then it must be an increasing signal
            sm.dbm = sm.AttackAvedbm; // use attack average value for output
            sm.DecayAvedbm = sm.AttackAvedbm; // set decay average to attack average value for next time
        }
        else
        { // signal is decreasing, so use decay average value
            sm.dbm = sm.DecayAvedbm;
        }

        if (sm.AttackAvedbmhz > sm.DecayAvedbmhz)
        { // if attack average is larger then it must be an increasing signal
            sm.dbmhz = sm.AttackAvedbmhz; // use attack average value for output
            sm.DecayAvedbmhz = sm.AttackAvedbmhz; // set decay average to attack average value for next time
        }
        else
        { // signal is decreasing, so use decay average value
            sm.dbmhz = sm.DecayAvedbmhz;
        }
    }
}

static void UiDriver_HandleSMeter(void)
{

	// Only in RX mode
	if(ts.txrx_mode == TRX_MODE_RX)
	{
		// This makes a portion of the S-meter go red if A/D clipping occurs
		//
		{
			static bool         clip_indicate = 0;

			const float *S_Meter_Cal_Ptr = S_Meter_Cal_dbm;
			uint32_t s_count = 0;

			// find corresponding signal level
			for (
					s_count = 1;
					(sm.dbm >= S_Meter_Cal_Ptr[s_count]) && (s_count < S_Meter_Cal_Size);
					s_count++)
			{
				// nothing to do here
			}

			sm.s_count = s_count; // preserve value for CAT level reading

			if(ads.adc_clip)	 		// did clipping occur?
			{
				if(!clip_indicate)	 	// have we seen it clip before?
				{
					UiDriver_DrawSMeter(Red);		// No, make the first portion of the S-meter red to indicate A/D overload
					clip_indicate = true;		// set flag indicating that we saw clipping and changed the screen (prevent continuous redraw)
				}
				ads.adc_clip = false;		// reset clip detect flag
			}
			else	 		// clipping NOT occur?
			{
				if(clip_indicate)	 	// had clipping occurred since we last visited this code?
				{
					UiDriver_DrawSMeter(White);					// yes - restore the S meter to a white condition
					clip_indicate = false;							// clear the flag that indicated that clipping had occurred
				}
			}

			// make sure that the S meter always reads something!
			UiDriver_UpdateTopMeterA((s_count>0) ? s_count : 1);
            UiDriver_DisplayDbm();
            //TEST
//            UiLcdHy28_PrintText(ts.Layout->PWR_NUM_IND.x, ts.Layout->PWR_NUM_IND.y,"X",Green,Black,0);
//            UiLcdHy28_PrintText(ts.Layout->PWR_NUM_IND.x, ts.Layout->PWR_NUM_IND.y," ",Green,Black,0);
            //ENDTEST
		}
	}
}



/**
 *
 * Power, SWR, ALC and Audio indicator handling
 */
static void UiDriver_HandleTXMeters(void)
{
	// Only in TX mode
	if(ts.txrx_mode != TRX_MODE_TX)
	{
		swrm.vswr_dampened = 0;		// reset averaged readings when not in TX mode
		swrm.fwd_pwr_avg = -1;
		swrm.rev_pwr_avg = -1;

        swrm.p_curr   = 0;
        swrm.fwd_calc = 0;
        swrm.rev_calc = 0;
        swrm.high_vswr_detected = false;

	}
	else
	{
		static uint8_t    old_power_level = 99;

		if(swrm.high_vswr_detected == true)
		{
			Board_RedLed(LED_STATE_TOGGLE);
		}

		// display FWD, REV power, in milliwatts - used for calibration - IF ENABLED
		if(swrm.pwr_meter_disp)
		{
			if((swrm.fwd_pwr_avg < 0) || (ts.power_level != old_power_level))  	// initialize with current value if it was zero (e.g. reset) or power level changed
			{
				swrm.fwd_pwr_avg = swrm.fwd_pwr;
			}
			else
			{
				swrm.fwd_pwr_avg = (swrm.fwd_pwr_avg * (1-PWR_DAMPENING_FACTOR)) + swrm.fwd_pwr * PWR_DAMPENING_FACTOR;	// apply IIR smoothing to forward power reading
			}

			if((swrm.rev_pwr_avg < 0) || (ts.power_level != old_power_level))  	// initialize with current value if it was zero (e.g. reset) or power level changed
			{
				swrm.rev_pwr_avg = swrm.rev_pwr;
			}
			else
			{
				swrm.rev_pwr_avg = (swrm.rev_pwr_avg * (1-PWR_DAMPENING_FACTOR)) + swrm.rev_pwr * PWR_DAMPENING_FACTOR; // apply IIR smoothing to reverse power reading
			}

			old_power_level = ts.power_level;		// update power level change detector
		}

		{
			char txt[16];
			const char* txp = NULL;
			if (swrm.pwr_meter_disp)
			{
				snprintf(txt,16, "%5d,%5d", (int)(swrm.fwd_pwr_avg*1000), (int)(swrm.rev_pwr_avg*1000));		// scale to display power in milliwatts
				txp = txt;
				swrm.pwr_meter_was_disp = 1;	// indicate the power meter WAS displayed
			}
			else if(swrm.pwr_meter_was_disp)	// had the numerical display been enabled - and it is now disabled?
			{
				txp = "           ";            // yes - overwrite location of numerical power meter display to blank it
				swrm.pwr_meter_was_disp = 0;	// clear flag so we don't do this again
			}
			if (txp != NULL)
			{
				UiLcdHy28_PrintText(ts.Layout->PWR_NUM_IND.x, ts.Layout->PWR_NUM_IND.y,txp,Green,Black,0);
			}
		}


		// Do selectable meter readings
		float   btm_mtr_val = 0.0;
		uint32_t btm_mtr_red_level = 13;


		if(ts.tx_meter_mode == METER_SWR)
		{
			if(swrm.fwd_pwr >= SWR_MIN_CALC_POWER)	 		// is the forward power high enough for valid VSWR calculation?
			{
				// (Do nothing/freeze old data if below this power level)
				if(swrm.vswr_dampened < 1)	// initialize averaging if this is the first time (e.g. VSWR <1 = just returned from RX)
				{
					swrm.vswr_dampened = swrm.vswr;
				}
				else
				{
					swrm.vswr_dampened = swrm.vswr_dampened * (1 - VSWR_DAMPENING_FACTOR) + swrm.vswr * VSWR_DAMPENING_FACTOR;
				}
				btm_mtr_val = swrm.vswr_dampened * 4;		// yes - four dots per unit of VSWR

				if((ts.expflags2 & EXPFLAGS2_SHOW_SWR_ONLY_TUNE) && (ts.txrx_mode == TRX_MODE_TX) && (!ts.tune))
				{
					btm_mtr_val = 0;
				}

			}
		}
		else if(ts.tx_meter_mode == METER_ALC)
		{
			btm_mtr_val = ads.alc_val;		// get TX ALC value
			btm_mtr_val *= btm_mtr_val;		// square the value
			btm_mtr_val = log10f(btm_mtr_val);	// get the log10
			btm_mtr_val *= -10;		// convert it to DeciBels and switch sign and then scale it for the meter
		}
		else if(ts.tx_meter_mode == METER_AUDIO)
		{
			btm_mtr_val = ads.peak_audio/10000;		// get a copy of the peak TX audio (maximum reference = 30000)
			ads.peak_audio = 0;					// reset the peak detect
			btm_mtr_val *= btm_mtr_val;			// square the value
			btm_mtr_val = log10f(btm_mtr_val);	// get the log10
			btm_mtr_val *= 10;					// convert to DeciBels and scale for the meter
			btm_mtr_val += 11;					// offset for meter

			btm_mtr_red_level = 22;	// setting the "red" threshold
		}

		// calculate and display RF power reading
		UiDriver_UpdateTopMeterA(swrm.fwd_pwr * 3);
		// show selected bottom meter value
		UiDriver_UpdateBtmMeter(btm_mtr_val, btm_mtr_red_level);   // update the meter, setting the "red" threshold
	}
}


static void UiDriver_CreateVoltageDisplay() {
	// Create voltage
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
	UiLcdHy28_PrintTextCentered (ts.Layout->PWR_IND.x,ts.Layout->PWR_IND.y,ts.Layout->LEFTBOXES_IND.w,   "--.- V",  COL_PWR_IND,Black,0);
	#else
	UiLcdHy28_PrintTextCentered (ts.Layout->PWR_IND.x,ts.Layout->PWR_IND.y,58,   "--.- V",  COL_PWR_IND,Black,0);
	#endif
#else
	UiLcdHy28_PrintTextCentered (ts.Layout->PWR_IND.x,ts.Layout->PWR_IND.y,58,   "--.- V",  COL_PWR_IND,Black,0);
#endif
}

static bool UiDriver_SaveConfiguration()
{
	bool savedConfiguration = true;

	const uint16_t scope_middle_y = sd.Slayout->full.h/2+sd.Slayout->full.y;

	const char* txp;
	uint16_t txc;

	switch (ts.configstore_in_use)
	{
	case CONFIGSTORE_IN_USE_FLASH:
		txp = "Saving settings to Flash Memory";
		break;
	case CONFIGSTORE_IN_USE_I2C:
		txp = "Saving settings to I2C EEPROM";
		break;
	default:
		txp = "Detected problems: Not saving";
		savedConfiguration = false;
	}
	UiLcdHy28_PrintTextCentered(sd.Slayout->full.x, scope_middle_y-6, sd.Slayout->full.w,txp,Blue,Black,0);

	if (savedConfiguration)
	{
		// save settings
		if (UiConfiguration_SaveEepromValues() == 0)
		{
			txp = "Saving settings finished";
			txc = Green;
		}
		else
		{
			txp = "Saving settings failed";
			txc = Red;
			savedConfiguration = false;
		}
		UiLcdHy28_PrintTextCentered(sd.Slayout->full.x, scope_middle_y+6, sd.Slayout->full.w,txp,txc,Black,0);
	}
	return savedConfiguration;
}


/*
 * @brief displays the visual information that power down is being executed and saves EEPROM if requested
 */
static void UiDriver_PowerDownCleanup(bool saveConfiguration)
{
	const char* txp;
	// Power off all - high to disable main regulator

	ts.powering_down = 1;   // indicate that we should be powering down

	UiSpectrum_Clear();   // clear display under spectrum scope

	// hardware based mute
	Codec_MuteDAC(true);  // mute audio when powering down

	txp = " ";
#ifndef SDR_AMBER_480_320
	UiLcdHy28_PrintTextCentered(60,148,240,txp,Blue2,Black,0);
	UiLcdHy28_PrintTextCentered(60,156,240,"Powering off...",Blue2,Black,0);
	UiLcdHy28_PrintTextCentered(60,168,240,txp,Blue2,Black,0);
#else
	UiLcdHy28_PrintTextCentered(120,148,240,txp,Blue2,Black,0);
	UiLcdHy28_PrintTextCentered(120,156,240,"Powering off...",Blue2,Black,0);
	UiLcdHy28_PrintTextCentered(120,168,240,txp,Blue2,Black,0);
#endif

	if (saveConfiguration)
	{
		UiDriver_SaveConfiguration();
	}
	else
	{
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
		UiLcdHy28_PrintTextCentered(60,176,260,"...without saving settings...",Blue,Black,0);
	#else
		UiLcdHy28_PrintTextCentered(110,176,260,"...without saving settings...",Blue,Black,0);
	#endif
#else
		UiLcdHy28_PrintTextCentered(110,176,260,"...without saving settings...",Blue,Black,0);
#endif
	}


	if(saveConfiguration)
	{
		UiConfiguration_SaveEepromValues();     // save EEPROM values
	}

	HAL_Delay(3000);
}



/*
 * @brief Display external voltage
 */
static void UiDriver_DisplayVoltage()
{
//    if(ts.show_wide_spectrum)
//    {
//        return;
//    }
	uint32_t low_power_threshold = ((ts.low_power_config & LOW_POWER_THRESHOLD_MASK) + LOW_POWER_THRESHOLD_OFFSET) * 10;
	// did we detect a voltage change?

	uint32_t col = COL_PWR_IND;  // Assume normal voltage, so Set normal color

	if (pwmt.voltage < low_power_threshold + 50)
	{
		col = Red;
	}
	else if (pwmt.voltage < low_power_threshold + 100)
	{
		col = Orange;
	}
	else if (pwmt.voltage < low_power_threshold + 150)
	{
		col = Yellow;
	}

	static uint8_t voltage_blink = 0;
	// in case of low power shutdown coming, we let the voltage blink with 1hz
	if (pwmt.undervoltage_detected == true && voltage_blink < 1 )
	{
		col = Black;
	}
	voltage_blink++;
	if (voltage_blink == 2)
	{
		voltage_blink = 0;
	}

	char digits[6];
	snprintf(digits,6,"%2ld.%02ld",pwmt.voltage/100,pwmt.voltage%100);
	UiLcdHy28_PrintText(ts.Layout->PWR_IND.x,ts.Layout->PWR_IND.y,digits,col,Black,0);
}

/**
 * @brief Measures Voltage and controls undervoltage detection
 * @returns true if display update is required, false if not
 */
static bool UiDriver_HandleVoltage(void)
{
	bool retval = false;
	// if this is set to true, we should update the display because something relevant for the user happened.

	// Collect samples
	if(pwmt.p_curr < POWER_SAMPLES_CNT)
	{
		// Add to accumulator
		pwmt.pwr_aver = pwmt.pwr_aver + HAL_ADC_GetValue(&hadc1);
		pwmt.p_curr++;
	}
	else
	{

		// Get average
		uint32_t val_p  = ((pwmt.pwr_aver/POWER_SAMPLES_CNT) * (ts.voltmeter_calibrate + 900))/2500;

		// Reset accumulator
		pwmt.p_curr     = 0;
		pwmt.pwr_aver   = 0;


		retval = pwmt.voltage != val_p;

		pwmt.voltage = val_p;


		uint32_t low_power_threshold = ((ts.low_power_config & LOW_POWER_THRESHOLD_MASK) + LOW_POWER_THRESHOLD_OFFSET) * 10;
		bool low_power_shutdown_enabled = (ts.low_power_config & LOW_POWER_ENABLE_MASK) == LOW_POWER_ENABLE;

		if (low_power_shutdown_enabled && (val_p < low_power_threshold ))
		{
			// okay, voltage is too low, we should indicate
			pwmt.undervoltage_detected = true;
			retval = true;

			if (ts.txrx_mode == TRX_MODE_RX)
			{
				if (ts.sysclock > ts.low_power_shutdown_time )         // only allow power-off in RX mode
				{
					UiDriver_PowerDownCleanup(true);
				}
			}
			else
			{
				ts.low_power_shutdown_time = ts.sysclock + LOW_POWER_SHUTDOWN_DELAY_TIME;
				// in tx mode, we extend the waiting time during the transmit, so that we don't switch off
				// right after a transmit but let the battery some time to "regenerate"
			}
		}
		else
		{
			if (pwmt.undervoltage_detected == true)
			{
				retval = true;
				pwmt.undervoltage_detected = false;
				Board_GreenLed(LED_STATE_ON);
			}
			ts.low_power_shutdown_time = ts.sysclock + LOW_POWER_SHUTDOWN_DELAY_TIME;
		}
	}

	return retval;
}

#if 0
/*
 * @brief Displays temp compensation value in a bar
 */
static void UiDriverUpdateLoMeter(uchar val,uchar active)
{
	static int last_active = 99;
	static uint32_t last_active_val = 99;
	uchar 	i,v_s = 3;
	int		clr = White;

	if (last_active != active)
	{
		last_active = active;
		last_active_val = val;
		// Full redraw
		for(i = 1; i < 26; i++)
		{
			if (active)
			{
				clr = val==i?Blue:White;
			}
			else
			{
				clr = Grey;
			}
			UiLcdHy28_DrawStraightLineTriple(((POS_TEMP_IND_X + 1) + i*4),((POS_TEMP_IND_Y + 21) - v_s),v_s,LCD_DIR_VERTICAL,clr);
		}
	}
	else if (active && (last_active_val != val))
	{
		// Partial redraw
		if (val>1 && val < 26) {
			UiLcdHy28_DrawStraightLineTriple(((POS_TEMP_IND_X + 1) + val*4),((POS_TEMP_IND_Y + 21) - v_s),v_s,LCD_DIR_VERTICAL,Blue);
		}
		if (last_active_val>1 && last_active_val < 26) {
			UiLcdHy28_DrawStraightLineTriple(((POS_TEMP_IND_X + 1) + last_active_val*4),((POS_TEMP_IND_Y + 21) - v_s),v_s,LCD_DIR_VERTICAL,White);
		}
		last_active_val = val;
	}
}
#endif

/**
 * \brief draws the the TCXO temperature display, has to be called once
 *
 * @param create set to true in order to draw the static parts of the UI too.
 * @param enabled set to true in order to enable actual display of temperature
 */
#define TEMP_DATA 43
void UiDriver_CreateTemperatureDisplay()
{
	const char *label, *txt;
	uint32_t label_color, txt_color;

	bool enabled = lo.sensor_present == true && RadioManagement_TcxoIsEnabled();

	label = "TCXO";
	label_color = Black;

	// Top part - name and temperature display
	UiLcdHy28_DrawEmptyRect(ts.Layout->TEMP_IND.x, ts.Layout->TEMP_IND.y,13,109,Grey);

	if (enabled)
	{
		txt = RadioManagement_TcxoIsFahrenheit()?"*---.-F":"*---.-C";
		txt_color = Grey;
	}
	else
	{
		if (lo.sensor_present == false)
		{
//			txt = "SENSOR!";
//			txt_color = Red;
            txt = "*  NOP ";
            txt_color = Grey;
		}
		else
		{
			txt = "*  OFF ";
			txt_color = Grey;
		}
	}

	// Label
	UiLcdHy28_PrintText((ts.Layout->TEMP_IND.x + 1), (ts.Layout->TEMP_IND.y + 1),label,label_color,Grey,0);
	// Lock Indicator
	UiLcdHy28_PrintText(ts.Layout->TEMP_IND.x + TEMP_DATA,(ts.Layout->TEMP_IND.y + 1), txt,txt_color,Black,0);	// show base string
}

/**
 * @brief display measured temperature and current state of TCXO
 * @param temp in tenth of degrees Celsius (10 == 1 degree C)
 */
static void UiDriver_DisplayTemperature(int temp)
{
	static int last_disp_temp = -100;
	uint32_t clr =  RadioManagement_TcxoGetMode() ==TCXO_ON ? Blue:Red;

	UiLcdHy28_PrintText(ts.Layout->TEMP_IND.x + TEMP_DATA,(ts.Layout->TEMP_IND.y + 1),"*",clr,Black,0);

	if (temp != last_disp_temp)
	{
		char out[10];
		char* txt_ptr;
		if((temp < 0) || (temp > 1000))  // is the temperature out of range?
		{
			txt_ptr = "RANGE!";
		}
		else {
			last_disp_temp = temp;

			int32_t ttemp = last_disp_temp;
			if(RadioManagement_TcxoIsFahrenheit())
			{
				ttemp = ((ttemp *9)/5) + 320;			// multiply by 1.8 and add 32 degrees
			}
			snprintf(out,10,"%3ld.%1ld",ttemp/10,(ttemp)%10);
			txt_ptr = out;
		}
		UiLcdHy28_PrintText(ts.Layout->TEMP_IND.x + TEMP_DATA + SMALL_FONT_WIDTH*1,(ts.Layout->TEMP_IND.y + 1),txt_ptr,Grey,Black,0);
	}
}

//*----------------------------------------------------------------------------
//* Function Name       : UiDriverHandleLoTemperature
//* Object              : display LO temperature and compensate drift
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
static void UiDriver_HandleLoTemperature()
{
	if (SoftTcxo_HandleLoTemperatureDrift())
	{
		UiDriver_DisplayTemperature(lo.temp/1000); // precision is 0.1 represent by lowest digit
	}
}



//*----------------------------------------------------------------------------
//* Function Name       : UiDriverEditMode
//* Object              :
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//*----------------------------------------------------------------------------
/*static void UiDriverEditMode()
{
	char symb[2];

	// Is edit mode ?
	if(kbs.set_mode != 1)
		return;

	// Key pressed
	if(kbs.last_char == 0)
		return;

	//printf("key = %02x ",kbs.last_char);

	// Handle CR
	if(kbs.last_char == 0x0a)
	{
		kbs.edit_item_id++;
		if(kbs.edit_item_id == 3)
			kbs.edit_item_id = 0;

		// Switch items
		switch(kbs.edit_item_id)
		{
			case 0:
			{
				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 5),(POS_SPECTRUM_IND_Y +  0),"Call:  ",White,Black,0);
				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 5),(POS_SPECTRUM_IND_Y + 15),"Loc:   ",Grey, Black,0);
				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 5),(POS_SPECTRUM_IND_Y + 30),"Power: ",Grey, Black,0);
				break;
			}

			case 1:
			{
				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 5),(POS_SPECTRUM_IND_Y +  0),"Call:  ",Grey,Black,0);
				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 5),(POS_SPECTRUM_IND_Y + 15),"Loc:   ",White, Black,0);
				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 5),(POS_SPECTRUM_IND_Y + 30),"Power: ",Grey, Black,0);
				break;
			}

			case 2:
			{
				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 5),(POS_SPECTRUM_IND_Y +  0),"Call:  ",Grey,Black,0);
				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 5),(POS_SPECTRUM_IND_Y + 15),"Loc:   ",Grey, Black,0);
				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 5),(POS_SPECTRUM_IND_Y + 30),"Power: ",White, Black,0);
				break;
			}

			default:
				break;
		}

		// Reset hor ptr
		kbs.edit_item_hor = 0;
	}
	else
	{
		symb[0] = kbs.last_char;
		symb[1] = 0;

		// Print items
		switch(kbs.edit_item_id)
		{
			case 0:
			{
				// Add to buffer
				kbs.item_0[kbs.edit_item_hor] = kbs.last_char;

				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 80 + (kbs.edit_item_hor*12)),(POS_SPECTRUM_IND_Y +  0),symb,Grey,Black,0);
				break;
			}

			case 1:
			{
				// Add to buffer
				kbs.item_1[kbs.edit_item_hor] = kbs.last_char;

				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 80 + (kbs.edit_item_hor*12)),(POS_SPECTRUM_IND_Y + 15),symb,Grey, Black,0);
				break;
			}

			case 2:
			{
				// Add to buffer
				kbs.item_2[kbs.edit_item_hor] = kbs.last_char;

				UiLcdHy28_PrintText((POS_SPECTRUM_IND_X + 80 + (kbs.edit_item_hor*12)),(POS_SPECTRUM_IND_Y + 30),symb,Grey, Black,0);
				break;
			}

			default:
				break;
		}

		// Move cursor right
		kbs.edit_item_hor++;
		if(kbs.edit_item_hor == 10)
			kbs.edit_item_hor = 0;
	}

	// Clear public
	kbs.last_char = 0;
}*/

typedef enum
{
	CONFIG_DEFAULTS_KEEP = 0,
	CONFIG_DEFAULTS_LOAD_FREQ,
	CONFIG_DEFAULTS_LOAD_ALL
} CONFIG_DEFAULTS;


static void UiDriver_WaitForBandMAndBandPorPWR(void)
{
    while((((UiDriver_IsButtonPressed(BUTTON_BNDM_PRESSED)) && (UiDriver_IsButtonPressed(BUTTON_BNDP_PRESSED))) == false) && UiDriver_IsButtonPressed(BUTTON_PWR_PRESSED) == false)
    {
        HAL_Delay(20);
        Keypad_Scan();
    }
}


/*
 * @brief Handles the loading of the configuration at startup (including the load of defaults if requested)
 * @returns false if it is a normal startup, true if defaults have been loaded
 */
static bool UiDriver_LoadSavedConfigurationAtStartup()
{

	bool retval = false;
	CONFIG_DEFAULTS load_mode = CONFIG_DEFAULTS_KEEP;

	if (UiDriver_IsButtonPressed(BUTTON_F1_PRESSED) && UiDriver_IsButtonPressed(BUTTON_F3_PRESSED) && UiDriver_IsButtonPressed(BUTTON_F5_PRESSED))
	{
		load_mode = CONFIG_DEFAULTS_LOAD_ALL;
	}
	else if (UiDriver_IsButtonPressed(BUTTON_F2_PRESSED) && UiDriver_IsButtonPressed(BUTTON_F4_PRESSED))
	{
		load_mode = CONFIG_DEFAULTS_LOAD_FREQ;
	}

	if(load_mode != CONFIG_DEFAULTS_KEEP)
	{
		// let us make sure, the user knows what he/she is doing
		// in case of change of mindes, do normal configuration load

		uint32_t clr_fg = White, clr_bg = Black;
		const char* top_line = "";

		switch (load_mode)
		{
		case CONFIG_DEFAULTS_LOAD_ALL:
			clr_bg = Red;
			clr_fg = White;
			top_line = "ALL DEFAULTS";
			break;
		case CONFIG_DEFAULTS_LOAD_FREQ:
			clr_bg = Yellow;
			clr_fg = Black;
			top_line = "FREQ/MODE DEFAULTS";
			break;
		default:
			break;
		}


		UiLcdHy28_LcdClear(clr_bg);							// clear the screen
		// now do all of the warnings, blah, blah...
		UiLcdHy28_PrintTextCentered(2,05, 316, top_line,clr_fg,clr_bg,1);
		UiLcdHy28_PrintTextCentered(2,35, 316, "-> LOAD REQUEST <-",clr_fg,clr_bg,1);

		UiLcdHy28_PrintTextCentered(2,70, 316,
				"If you don't want to do this\n"
				"press POWER button to start normally.",clr_fg,clr_bg,0);

		UiLcdHy28_PrintTextCentered(2,120, 316,
				"If you want to load default settings\n"
				"press and hold BAND+ AND BAND-.\n"
				"Settings will be saved at POWEROFF",clr_fg,clr_bg,0);

		// On screen delay									// delay a bit...
		HAL_Delay(5000);

		// add this for emphasis
		UiLcdHy28_PrintTextCentered(2,195, 316,
				"Press BAND+ and BAND-\n"
				"to confirm loading",clr_fg,clr_bg,0);

		UiDriver_WaitForBandMAndBandPorPWR();

		const char* txp;

		if(UiDriver_IsButtonPressed(BUTTON_PWR_PRESSED))
		{
			clr_bg = Black;							// clear the screen
			clr_fg = White;
			txp = "...performing normal start...";

			load_mode = CONFIG_DEFAULTS_KEEP;
			retval = false;
		}
		else
		{
			txp = "...loading defaults in progress...";
			// call function to load values - default instead of EEPROM
			retval = true;
			ts.menu_var_changed = true;
		}
		UiLcdHy28_LcdClear(clr_bg);                         // clear the screen
		UiLcdHy28_PrintTextCentered(2,108,316,txp,clr_fg,clr_bg,0);
		HAL_Delay(5000);
	}

	bool load_freq_mode_defaults = false;
	bool load_eeprom_defaults = false;
	switch (load_mode)
	{
	case CONFIG_DEFAULTS_LOAD_ALL:
		load_eeprom_defaults = true;                           // yes, set flag to indicate that defaults will be loaded instead of those from EEPROM
		break;
	case CONFIG_DEFAULTS_LOAD_FREQ:
		load_freq_mode_defaults = true;
		break;
	default:
		break;
	}

	UiConfiguration_LoadEepromValues(load_freq_mode_defaults, load_eeprom_defaults);

	return retval;
}

//*----------------------------------------------------------------------------
//* Function Name       : UiCheckForPressedKey
//* Object              : Used for testing keys on the front panel
//* Input Parameters    :
//* Output Parameters   :
//* Functions called    :
//* Comments            : If a button (other than "POWER") is held during power-up a screen is displayed that shows which key (if any)
//  Comments            : is being pressed.  If multiple keys are being pressed, only the one with the highest precedence is displayed.  The order of decreasing
//  Comments            : precedence is:  M2, G3, G2, BNDM, G4, M3, STEPM, STEPP, M1, M3, F1, F2, F4, BNDP, F5, G1 and POWER.  [KA7OEI October, 2015]
//*----------------------------------------------------------------------------
static void UiDriver_KeyTestScreen()
{
	ushort p_o_state = 0, rb_state = 0, new_state = 0;
	uint32_t poweroffCount = 0, rebootCount = 0;

	// int direction;

	uint32_t keyScanState = 0;

    Keypad_Scan(); // read and map the keys to their logical buttons
    // not all keys may have a button or some keys may go to the same button

	if (UiDriver_IsButtonPressed(TOUCHSCREEN_ACTIVE))
	{
	    UiLcdHy28_TouchscreenHasProcessableCoordinates();
	    // touchscreen was pressed:
	    // wait a little and see if touch is still pressed
	    // some TRX seem to see touchscreen events even if not pressed
	    HAL_Delay(500);
	}

	Keypad_Scan();

    keyScanState = Keypad_KeyStates();	// remember which one was pressed

	if(Keypad_IsAnyKeyPressed()) {			// at least one button was pressed

        char txt_buf[40];
        const char* txt;
        uint32_t encoderCount = 0;
        const uint32_t clr_fg = White;
        const uint32_t clr_bg = Blue;

		UiLcdHy28_LcdClear(Blue);							// clear the screen
        UiLcdHy28_PrintTextCentered(2,05,ts.Layout->Size.x-4,"INPUT TEST SCREEN",clr_fg,clr_bg,1);

		snprintf(txt_buf,40,"Keys Initial: %08lx",keyScanState);
		UiLcdHy28_PrintTextCentered(0,30,ts.Layout->Size.x,txt_buf,White,Blue,0);

		UiLcdHy28_PrintTextCentered(0,70,ts.Layout->Size.x,"press & hold POWER button to poweroff\npress & hold BAND- button to reboot",White,Blue,0);

		for(;;)	 		// get stuck here for test duration
		{
			uint32_t idxFirstPressedButton = 99;		// load with flag value
			uint32_t numOfPressedButtons = 0;

			// we slow down the loop a bit so that our wait counters take some time to go down.
            HAL_Delay(10);

	        Keypad_Scan();
	        // read and map the hw keys to their logical buttons
	        // not all keys may have a button or some keys may go to the same button


	        uint32_t newKeyScanState = Keypad_KeyStates();   // check which hw keys are pressed

	        if (newKeyScanState != keyScanState)
	        {
	            keyScanState = newKeyScanState;
	            snprintf(txt_buf,40,"Keys Current: %08lx",keyScanState);
	            UiLcdHy28_PrintTextCentered(0,45,ts.Layout->Size.x,txt_buf,White,Blue,0);
	        }


	        //  now find out which buttons are pressed (logical buttons, not hw keys)
			for(int buttonIdx = 0; buttonIdx < BUTTON_NUM ; buttonIdx++)
			{
				// scan all buttons
				if(UiDriver_IsButtonPressed(buttonIdx))
				{
					// is this button pressed?
					numOfPressedButtons++;
					if(idxFirstPressedButton == 99)						// is this the first button pressed?
					{
						idxFirstPressedButton = buttonIdx;						// save button number
					}
				}
			}

			if(idxFirstPressedButton == BUTTON_BNDM_PRESSED && new_state == 0)	// delay if BANDM was used to enter button test mode
			{
				rebootCount = 0;
				new_state = 1;
			}


			// now find out if an encoder was moved (we detect this by seeing a encoder value != 0)
			int32_t encoderDirection;

			uint32_t encoderIdx;
			for(encoderIdx = 0; encoderIdx < ENC_MAX; encoderIdx++)
			{
				encoderDirection = UiDriverEncoderRead(encoderIdx);
				if(encoderDirection != 0)
				{
					encoderCount = 200;
					break;
				}
			}

			if(encoderIdx != ENC_MAX)
			{
				snprintf(txt_buf,40," Encoder %ld <%s>", encoderIdx+1, encoderDirection>0 ? "right":"left");		// building string for encoders
				idxFirstPressedButton = BUTTON_NUM+encoderIdx;					// add encoders behind buttons;
			}

			if (idxFirstPressedButton < BUTTON_NUM)
			{
				txt = Keypad_GetLabelOfButton( idxFirstPressedButton );
			}
			else
			{
				txt = NULL;
			}
			switch(idxFirstPressedButton)	 				// decode keyPin to text
			{
			case	BUTTON_PWR_PRESSED:
				if(poweroffCount > 75)
				{
					txt = "powering off...";
					p_o_state = 1;
				}
				poweroffCount++;
				break;
			case	BUTTON_BNDM_PRESSED:
				if(rebootCount > 75)
				{
					txt = "rebooting...";
					rb_state = 1;
				}
				rebootCount++;
				break;
			case	TOUCHSCREEN_ACTIVE:

				if (UiLcdHy28_TouchscreenHasProcessableCoordinates())
				{

					snprintf(txt_buf,40,"x/y: %04d/%04d x/y raw: %04x/%04x",ts.tp->hr_x,ts.tp->hr_y,ts.tp->xraw,ts.tp->yraw);	//show touched coordinates
					UiLcdHy28_PrintTextCentered(2,216,ts.Layout->Size.x-4,txt_buf,White,Blue,0);           // identify button on screen
					UiLcdHy28_DrawColorPoint(ts.tp->hr_x,ts.tp->hr_y,White);

					txt = "Touch";
				}
				else
				{
					if (mchf_touchscreen.present)
					{
						txt = "Touch (no coord.)";
					}
					else
					{
						txt = "Touch (no cntrlr)";
					}
				}
				break;
			case	BUTTON_NUM+ENC1:							// handle encoder event
			case	BUTTON_NUM+ENC2:
			case	BUTTON_NUM+ENC3:
            case    BUTTON_NUM+ENCFREQ:
			    txt = txt_buf;
			    break;
			default:
				if (txt == NULL)
				{
					if(encoderCount == 0)
					{
						txt = "<no key>";				// no keyPin pressed
					}
					else
					{
						encoderCount--;
					}
					poweroffCount = 0;
					rebootCount = 0;
				}
			}

			if(txt != NULL)
			{
				UiLcdHy28_PrintTextCentered(0,120,ts.Layout->Size.x,txt,White,Blue,1);			// identify button on screen
			}

			snprintf(txt_buf,40, "# of buttons pressed: %ld  ", numOfPressedButtons);
			UiLcdHy28_PrintTextCentered(0,160,ts.Layout->Size.x,txt_buf,White,Blue,0);			// show number of buttons pressed on screen

			if(ts.tp->present)			// show translation of touchscreen if present
			{
				txt = "Touch Coordinates:";
			}
			else
			{
				txt = "Touch Controller not present";
			}

			UiLcdHy28_PrintTextCentered(0,200,ts.Layout->Size.x,txt,White,Blue,0);

			if(p_o_state == 1)
			{
                if(idxFirstPressedButton != BUTTON_PWR_PRESSED)
                {
                    Board_Powerdown();
                }
				// never reached
			}
			if(rb_state == 1)
			{
				if(idxFirstPressedButton != BUTTON_BNDM_PRESSED)
				{
					Board_Reboot();
				}
			}
		}
	}
}
//cross size definitions, must be odd
#define CrossSizeH 11
#define CrossSizeV 11
static void DrawCross(int16_t* coord,uint16_t color)
{
	UiLcdHy28_DrawStraightLine(coord[0]-(CrossSizeH/2), coord[1],CrossSizeH,        LCD_DIR_HORIZONTAL,color);
	UiLcdHy28_DrawStraightLine(coord[0], coord[1]-(CrossSizeV/2),CrossSizeV,        LCD_DIR_VERTICAL,color);
}


/*
 * @brief Touchscreen Calibration function
 * @returns false if it is a normal startup, true if touchscreen has been calibrated
 */

#define ARM_MATH_MATRIX_CHECK
#define Touch_ShowTestscreen

static void UiDriver_TouchscreenCalibrationRun(void)
{
    UiLcdHy28_TouchscreenReadCoordinates();
    ts.tp->state = TP_DATASETS_NONE;
    uint16_t MAX_X=ts.Layout->Size.x; uint16_t MAX_Y=ts.Layout->Size.y;

    int16_t cross[5][4] =
    {
            {      20,      20,0,0},
            {MAX_X-20,      20,0,0},
            {      20,MAX_Y-20,0,0},
            {MAX_X-20,MAX_Y-20,0,0},
            { MAX_X/2, MAX_Y/2,0,0},
    };

    //reset calibration coefficients before acquiring points
    for(int16_t m=0; m<6; m++)
    {
        ts.tp->cal[m]=0;
    }

    ts.tp->cal[0]=65536;
    ts.tp->cal[4]=65536;

    for (int16_t idx = 0; idx < 5; idx++)
    {
        UiDriver_DoCrossCheck(cross[idx]);
    }

    //calibration algorithm based on publication:
    //"Calibration in touch-screen systems" Texas Instruments
    //Analog Applications Journal 3Q 2007

    /*//test vectors
    int16_t cross[0][4] = {     128,     384,1698,2258};
    int16_t cross[1][4] = {      64,     192, 767,1149};
    int16_t cross[2][4] = {     192,     192,2807,1327};
    int16_t cross[3][4] = {     192,     576,2629,3367};
    int16_t cross[4][4] = {      64,     576, 588,3189};*/

    //matrices field definitions
    float mA[3*5];
    float mAT[3*5];
    float mATAinv[3*3];
    float mbuff[3*3];
    float mcom[3*5];
    float mX[5];
    float mY[5];
    float mABC[3];
    float mDEF[3];

    //matrix data init
    for (int m=0; m < 5; m++)
    {
        mA[3*m+0]=cross[m][2];
        mA[3*m+1]=cross[m][3];
        mA[3*m+2]=1.0;
        mX[m]= cross[m][0];
        mY[m]= cross[m][1];
    }

    //create matrices instances
    arm_matrix_instance_f32 m_A,m_AT,m_ATAinv,m_X,m_Y,m_ABC,m_DEF,m_buff,m_com;

    //init of matrices
    arm_mat_init_f32(&m_A,5,3,mA);
    arm_mat_init_f32(&m_AT,3,5,mAT);
    arm_mat_init_f32(&m_ATAinv,3,3,mATAinv);
    arm_mat_init_f32(&m_X,5,1,mX);
    arm_mat_init_f32(&m_Y,5,1,mY);
    arm_mat_init_f32(&m_ABC,3,1,mABC);
    arm_mat_init_f32(&m_DEF,3,1,mDEF);
    arm_mat_init_f32(&m_buff,3,3,mbuff);
    arm_mat_init_f32(&m_com,3,5,mcom);

    //real computation
    arm_mat_trans_f32(&m_A,&m_AT);           //A^T           size 5x3 -> 3x5
    arm_mat_mult_f32(&m_AT,&m_A,&m_buff);        //A^T x A   size 3x5 * 5x3 -> 3x3
    arm_mat_inverse_f32(&m_buff,&m_ATAinv);  //(A^T x A)^-1  size 3x3
    arm_mat_mult_f32(&m_ATAinv,&m_AT,&m_com);//(A^T x A)^-1 x A^T   m_com is common matrix for estimating coefficients for X and Y      size 3x3 * 3x5 -> 3x5

    arm_mat_mult_f32(&m_com,&m_X,&m_ABC);   //calculating the coefficients for X data    size 3x5 * 5x1  -> 3x1
    arm_mat_mult_f32(&m_com,&m_Y,&m_DEF);   //calculating the coefficients for Y data    size 3x5 * 5x1  -> 3x1

    //store cal parameters
    for (int m=0; m < 3; m++)
    {
        ts.tp->cal[m]=mABC[m]*65536;
        ts.tp->cal[m+3]=mDEF[m]*65536;
    }
}

static bool UiDriver_TouchscreenCalibration()
{
	bool retval = false;
	uint16_t MAX_X=ts.Layout->Size.x; uint16_t MAX_Y=ts.Layout->Size.y;

    bool run_calibration = false;

    const uint32_t clr_bg = Black;
    const uint32_t clr_fg = White;

    Keypad_Scan();

    //if (UiDriver_IsButtonPressed(TOUCHSCREEN_ACTIVE) && UiDriver_IsButtonPressed(BUTTON_F5_PRESSED))
    if (UiDriver_IsButtonPressed(TOUCHSCREEN_ACTIVE))
    {
        //wait for a moment to filter out some unwanted spikes
        HAL_Delay(500);
        Keypad_Scan();

        if(UiDriver_IsButtonPressed(TOUCHSCREEN_ACTIVE))
        {

            UiLcdHy28_LcdClear(clr_bg);

            if (ts.tp->present)
            {
                // now do all of the warnings, blah, blah...
                UiLcdHy28_PrintTextCentered(2,05,MAX_X-4,"TOUCH CALIBRATION",clr_fg,clr_bg,1);
                UiLcdHy28_PrintTextCentered(2, 70, MAX_X-4, "If you don't want to do this\n"
                        "press POWER button to start normally.\n"
                        " Settings will be saved at POWEROFF"
                        ,clr_fg,clr_bg,0);

                // delay a bit...
                HAL_Delay(3000);

                // add this for emphasis
                UiLcdHy28_PrintTextCentered(2, 195, MAX_X-4, "Press BAND+ and BAND-\n"
                        "to start calibration",clr_fg,clr_bg,0);

                UiDriver_WaitForBandMAndBandPorPWR();

                if (UiDriver_IsButtonPressed(BUTTON_PWR_PRESSED))
                {
                    UiLcdHy28_LcdClear(Black);							// clear the screen
                    UiLcdHy28_PrintTextCentered(2,108,MAX_X-4,"      ...performing normal start...",White,Black,0);
                    HAL_Delay(3000);
                }
                else
                {
                    run_calibration = true;
                }
            }
            else
            {
                UiLcdHy28_PrintTextCentered(2,05,MAX_X-4,"TOUCHSCREEN ERROR",clr_fg,clr_bg,1);
                UiLcdHy28_PrintTextCentered(2, 70, MAX_X-4, "A touchscreen press was detected\n"
                        "but no touchscreen controller found\n"
                        "Calibration cannot be executed!"
                        ,clr_fg,clr_bg,0);
                // delay a bit...
                HAL_Delay(3000);
            }
		}
	}

	if (run_calibration)
	{
	    UiLcdHy28_LcdClear(clr_bg);
	    UiLcdHy28_PrintTextCentered(2,70, MAX_X-4,
	            "On the next screen crosses will appear.\n"
	            "Touch as exact as you can on the middle\n"
	            "of each cross. After three valid\n"
	            "samples position of cross changes.\n"
	            "Repeat until the five test positions\n"
	            "are finished.",clr_fg,clr_bg,0);

	    UiLcdHy28_PrintTextCentered(2,195,MAX_X-4,"Touch at any position to start.",clr_fg,clr_bg,0);

 	    UiDriver_WaitForButtonPressed(TOUCHSCREEN_ACTIVE);

	    UiLcdHy28_LcdClear(clr_bg);
	    UiLcdHy28_PrintTextCentered(2,100,MAX_X-4,"Wait one moment please...",Yellow,clr_bg,0);
	    HAL_Delay(1000);

	    UiDriver_TouchscreenCalibrationRun();

	    UiLcdHy28_LcdClear(clr_bg);

#ifdef Touch_ShowTestscreen
	    UiLcdHy28_PrintTextCentered(2, 195, MAX_X-4, "Press BAND+ and BAND-\n"
	            "to run drawing on screen\n"
	            "or POWER to boot",clr_fg,clr_bg,0);

	    UiDriver_WaitForBandMAndBandPorPWR();

	    if (UiDriver_IsButtonPressed(BUTTON_PWR_PRESSED))
	    {
	        UiLcdHy28_LcdClear(Black);                          // clear the screen
	        UiLcdHy28_PrintTextCentered(2,108,MAX_X-4,"      ...performing normal start...",White,Black,0);
	    }
	    else
        {
            UiLcdHy28_LcdClear(clr_bg);
            UiLcdHy28_PrintTextCentered(2, MAX_Y/2-8, MAX_X-4, "Test screen.\n"
                    "You can draw by pressing the screen.\n"
                    "Press Power to boot",clr_fg,clr_bg,0);
            while(1)
            {
                do
                {
                    HAL_Delay(10);
                    Keypad_Scan();
                } while (UiDriver_IsButtonPressed(TOUCHSCREEN_ACTIVE) == false && UiDriver_IsButtonPressed(BUTTON_PWR_PRESSED) == false);

                if(UiDriver_IsButtonPressed(BUTTON_PWR_PRESSED) == true)
                {
                    UiLcdHy28_LcdClear(Black);                          // clear the screen
                    UiLcdHy28_PrintTextCentered(2,108,MAX_X-4,"      ...performing normal start...",White,Black,0);
                    break;
                }

                if (UiLcdHy28_TouchscreenHasProcessableCoordinates())
                {
                    //          *xt_corr += (ts.tp->hr_x - cross[0]);
                    //          *yt_corr += (ts.tp->hr_y - cross[1]);
                    UiLcdHy28_DrawColorPoint(ts.tp->hr_x,ts.tp->hr_y,White);
                }

            }
        }
#endif
	    HAL_Delay(2000);
	    retval = true;
	    ts.menu_var_changed = true;
	}
	return retval;
}

#define CrossCheckCount 3
void UiDriver_DoCrossCheck(int16_t cross[])
{
	uint16_t MAX_X=ts.Layout->Size.x;
	uint32_t clr_fg, clr_bg;
	clr_bg = Black;
	clr_fg = White;

	UiLcdHy28_LcdClear(clr_bg);
	DrawCross(cross,clr_fg);

	char txt_buf[40];
	uchar datavalid = 0, samples = 0;

	int16_t* xt_corr=&cross[2];
	int16_t* yt_corr=&cross[3];

	*xt_corr=0;
	*yt_corr=0;

	do
	{
	    UiDriver_WaitForButtonPressed(TOUCHSCREEN_ACTIVE);

		if (UiLcdHy28_TouchscreenHasProcessableCoordinates())
		{
			//if(abs(ts.tp->hr_x - cross[0]) < MaxTouchError && abs(ts.tp->hr_y - cross[1]) < MaxTouchError)
			//{
				datavalid++;
				*xt_corr += ts.tp->hr_x;
				*yt_corr += ts.tp->hr_y;
				clr_fg = Green;
				snprintf(txt_buf,40,"Try (%d) error: x = %+d / y = %+d",datavalid,ts.tp->hr_x-cross[0],ts.tp->hr_y-cross[1]);	//show misajustments
			/*}
			else
			{
				clr_fg = Red;
				snprintf(txt_buf,40,"Try (%d) BIG error: x = %+d / y = %+d",samples,ts.tp->hr_x-cross[0],ts.tp->hr_y-cross[1]);	//show misajustments
			}*/
			samples++;
			UiLcdHy28_PrintTextCentered(2,70,MAX_X-4,txt_buf,clr_fg,clr_bg,0);

			snprintf(txt_buf,40,"RAW: x = %+d / y = %+d",ts.tp->xraw,ts.tp->yraw);	//show misajustments
			UiLcdHy28_PrintTextCentered(2,85,MAX_X-4,txt_buf,clr_fg,clr_bg,0);
			ts.tp->state = TP_DATASETS_PROCESSED;
		}
	}
	while(datavalid < CrossCheckCount);

	UiLcdHy28_PrintTextCentered(2,100,MAX_X-4,"Wait one moment please...",Yellow,clr_bg,0);

	*xt_corr/=CrossCheckCount; //average the data
	*yt_corr/=CrossCheckCount;

	HAL_Delay(2000);
}


static uint16_t startUpScreen_nextLineY;
static bool startUpError = false;

/**
 * @brief use this method to report initialization problems on splash screen, may only be used during splash screen presence (!)
 *
 * @param isProblem if set to true, the problem is reported on screen, otherwise nothing is done
 * @param txt pointer to a text string characterizing the problem detected
 *
 */
void UiDriver_StartupScreen_LogIfProblem(bool isProblem, const char* txt)
{
	if (isProblem)
	{
		startUpScreen_nextLineY = UiLcdHy28_PrintTextCentered(ts.Layout->StartUpScreen_START.x,startUpScreen_nextLineY,320,txt,Black,Red3,0);
		startUpError = true;
	}
}

static uint16_t fw_version_number_major = 0;    // save new F/W version
static uint16_t fw_version_number_release = 0;
static uint16_t fw_version_number_minor = 0;

/**
 * @returns true if the firmware version is different from version in loaded configuration settings.
 */
static bool UiDriver_FirmwareVersionCheck(void)
{

	fw_version_number_major = atoi(UHSDR_VER_MAJOR);    // save new F/W version
	fw_version_number_release = atoi(UHSDR_VER_RELEASE);
	fw_version_number_minor = atoi(UHSDR_VER_MINOR);

	return ((ts.version_number_major != fw_version_number_major) || (ts.version_number_release != fw_version_number_release) || (ts.version_number_minor != fw_version_number_minor));        // Yes - check for new version
}
/**
 * @brief basically does nothing but settiSng the firmware number of configuration to number of running fw
 */
static void UiDriver_FirmwareVersionUpdateConfig(void)
{

	if (UiDriver_FirmwareVersionCheck())
	{
		ts.version_number_major = fw_version_number_major;    // save new F/W version
		ts.version_number_release = fw_version_number_release;
		ts.version_number_minor = fw_version_number_minor;

	}
}


/**
 * @brief show initial splash screen, we run AFTER the configuration storage has been initialized!
 * @param hold_time how long the screen to be shown before proceeding (in ms)
 */
void UiDriver_StartUpScreenInit()
{
	char   tx[100];
	uint32_t clr;
	// Clear all
	UiLcdHy28_LcdClear(Black);
	uint16_t nextY = ts.Layout->StartUpScreen_START.y;
	uint16_t posX  = ts.Layout->StartUpScreen_START.x;
#ifndef SDR_AMBER
	snprintf(tx,100,"%s",DEVICE_STRING);
#else
	snprintf(tx,100,"%s","AMBER Transceiver");
#endif
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY, 320, tx, Cyan, Black, 1);

#ifdef TRX_HW_LIC
	snprintf(tx,100,"Hardware License: %s",TRX_HW_LIC);
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY + 3, 320, tx, White,Black, 0);
#endif
#ifdef TRX_HW_CREATOR
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY, 320, TRX_HW_CREATOR, White,Black, 0);
#endif
#ifdef SDR_AMBER
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY, 320, "UB8JDC Korenev Yuri, ub8jdc@mail.ru", White,Black, 0);
#endif

//ADD Logo Tux
char line01_logo[]="  _    _ _    _  _____ _____  _____    ";
char line02_logo[]=" | |  | | |  | |/ ____|  __ \\|  __ \\  ";
char line03_logo[]=" | |  | | |__| | (___ | |  | | |__) | ";
char line04_logo[]=" | |  | |  __  |\\___ \\| |  | |  _  /  ";
char line05_logo[]=" | |__| | |  | |____) | |__| | | \\ \\  ";
char line06_logo[]="  \\____/|_|  |_|_____/|_____/|_|  \\_\\ ";



//	snprintf(tx,100,"%s%s","UHSDR Vers. ",UiMenu_GetSystemInfo(&clr,INFO_FW_VERSION));
//	nextY = UiLcdHy28_PrintTextCentered(ts.Layout->StartUpScreen_START.x, nextY + 8, 320, tx, Yellow, Black, 1);
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY, 320, line01_logo, Yellow, Black, 0);
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY, 320, line02_logo, Yellow, Black, 0);
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY, 320, line03_logo, Yellow, Black, 0);
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY, 320, line04_logo, Yellow, Black, 0);
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY, 320, line05_logo, Yellow, Black, 0);
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY, 320, line06_logo, Yellow, Black, 0);

	
	
	snprintf(tx,100,"%s%s%s%s","Firmware:",UiMenu_GetSystemInfo(&clr,INFO_FW_VERSION), " License:",UHSDR_LICENCE);
	nextY = UiLcdHy28_PrintTextCentered(posX, nextY + 8, 320, tx, White, Black, 0);

	// show important error status
	startUpScreen_nextLineY = nextY + 8; // reset y coord to first line of error messages

	UiLcdHy28_BacklightEnable(true);

}

void UiDriver_StartUpScreenFinish()
{
	const char* txp;
	char   tx[100];
	uint32_t clr, fg_clr;

	uint32_t hold_time;

	bool osc_present_problem = osc->isPresent() == false;

	UiDriver_StartupScreen_LogIfProblem(osc_present_problem, "Local Oscillator NOT Detected!");

	if(!Si5351a_IsPresent() && RadioManagement_TcxoIsEnabled())
	{
		UiDriver_StartupScreen_LogIfProblem(lo.sensor_present == false, "MCP9801 Temp Sensor NOT Detected!");
	}

	if(ts.configstore_in_use == CONFIGSTORE_IN_USE_ERROR)                                   // problem with EEPROM init
	{
#ifdef USE_CONFIGSTORAGE_FLASH
	    UiDriver_StartupScreen_LogIfProblem(ts.ee_init_stat != HAL_OK, "Config Flash Error");
#endif
	    if (SerialEEPROM_eepromTypeDescs[ts.ser_eeprom_type].size == 0)
	    {
	        snprintf(tx,100,"Config EEPROM: %s", SerialEEPROM_eepromTypeDescs[ts.ser_eeprom_type].name);
	        UiDriver_StartupScreen_LogIfProblem(true, tx);
	    }
	}

	if(!Si5351a_IsPresent()) {
	  UiDriver_StartupScreen_LogIfProblem((HAL_ADC_GetValue(&hadc2) > MAX_VSWR_MOD_VALUE) && (HAL_ADC_GetValue(&hadc3) > MAX_VSWR_MOD_VALUE),
			"SWR Bridge resistor mod NOT completed!");
	}

	// we report this problem only if we are theoretically able to transmit
	// and tx was not disabled such as in a RX only device
    if (RadioManagement_IsTxDisabled() == false && osc_present_problem == false)
    {
        bool pa_bias_problem = ts.pa_bias == 0;
        UiDriver_StartupScreen_LogIfProblem(pa_bias_problem,
              "PA Bias is 0, TX not possible");
    }

	if (UiDriver_FirmwareVersionCheck())
	{
		hold_time = 10000; // 10s
		txp = "Firmware change detected!\nPlease review settings!";
		startUpScreen_nextLineY = UiLcdHy28_PrintTextCentered(ts.Layout->StartUpScreen_START.x,startUpScreen_nextLineY + 10,320,txp,White,Black,0);

		UiDriver_FirmwareVersionUpdateConfig();
	}


	if(startUpError == true)
	{
		hold_time = 15000; // 15s
		//hold_time = 3000; // 3s
		txp = "Boot Delay because of Errors or Warnings";
		clr = Red3;
		fg_clr = Black;
	}
	else
	{
		hold_time = 3000; // 3s
		txp = "...starting up normally...";
		clr =  Black;
		fg_clr = Green;
	}

	UiLcdHy28_PrintTextCentered(ts.Layout->StartUpScreen_START.x,startUpScreen_nextLineY + 10,320,txp,fg_clr,clr,0);

	HAL_Delay(hold_time);

	if((ts.expflags1 & EXPFLAGS1_WIDE_SPEC_DEF) && (ts.Layout->Size.x == 320)) // Default wide spectrum on 320x240 display
	{
	ts.show_wide_spectrum = 1;
	ts.Layout = &LcdLayouts[LcdLayout_320x240_ws];
	}

	if(!(ts.expflags1 & EXPFLAGS1_TUNE_HELPER_DEFENABLE))
	{
		cw_decoder_config.snap_enable = true;
	}

	UiDriver_CreateDesktop();
}

// UiAction_... are typically small functions to execute a specific ui function initiate by a key press or touch event
// they take no argument and return nothing. If possible, try to keep the function atomic and independent from the
// key or touch region it is assigned to. I.e. it is better not to implement 2 functions based on menu mode or not here
// this logic should done separately so that the resulting action is reusable in different activation scenarios (touch or key or even CAT)
// If execution of an action is not applicable all the times it is necessary to check if the function should check that
// by itself and become a "No operation" or even issues a message, or if this is to be implicitly handled by the use of the
// function only in certain modes of operation through the modes tables.

// TODO: Make Atomic
static void UiAction_ChangeLowerMeterDownOrSnap(void)
{

	sc.snap = 1;

	// Not in MENU mode - select the METER mode
//	decr_wrap_uint8(&ts.tx_meter_mode,0,METER_MAX-1);
//	UiDriver_DeleteMeters();
//	UiDriver_CreateMeters();    // redraw meter
}

void UiAction_ChangeLowerMeterUp()
{
	incr_wrap_uint8(&ts.tx_meter_mode,0,METER_MAX-1);
	UiDriver_DeleteMeters();
	UiDriver_CreateMeters();	// redraw meter
#ifdef SDR_AMBER
  #ifndef SDR_AMBER_480_320
  UiLcdHy28_PrintTextVertLen(308, 43, "Amber", 5, Grey4, Black);
  #else
  UiLcdHy28_PrintTextVertLen(468, 47, "Amber", 5, Grey4, Black);
  #endif
#endif
}

/**
 *
 * @param dsp_mode a valid dsp mode id
 * @return true if dsp_mode is currently available, false otherwise
 */
static bool UiDriver_IsDspModePermitted(dsp_mode_t dsp_mode)
{
    bool neg_retval = (dsp_mode >= DSP_SWITCH_MAX);

    // prevent NR AND NOTCH or NOTCH, when in CW
    neg_retval |= ts.dmod_mode == DEMOD_CW && ( dsp_mode == DSP_SWITCH_NR_AND_NOTCH || dsp_mode == DSP_SWITCH_NOTCH);

    // prevent NR AND NOTCH, when in AM and decimation rate equals 2 --> high CPU load)
    neg_retval |= (dsp_mode == DSP_SWITCH_NR_AND_NOTCH) && (ts.dmod_mode == DEMOD_AM) && (ads.decimated_freq >= 24000);

    // prevent using a mode not enabled in the dsp mode selection (i.e. user configured it to be not used, although available)
    //neg_retval |= (ts.dsp.mode_mask & (1<<dsp_mode)) == 0;

    // not forbidden, so return true;
    return neg_retval == false;
}

/**
 * Request a dsp mode to be activated. If the request mode is not valid, try to find the "next" valid one. In worst case this
 * is "DSP OFF" which is always permitted.
 * @param new_dsp_mode request new dsp mode, can be any value, too large values cause starting at lowest mode
 */
void UiDriver_UpdateDSPmode(uint8_t new_dsp_mode)
{

	//loop for detection of first possible DSP function to switch it on if others are disabled/not allowed
	for(int i=0; i < DSP_SWITCH_MAX; i++)
	{

		if (new_dsp_mode >= DSP_SWITCH_MAX)
		{
		    new_dsp_mode = DSP_SWITCH_OFF; // flip round
		}

		if (UiDriver_IsDspModePermitted(new_dsp_mode))
		{
		    ts.dsp.mode = new_dsp_mode;
		    break;
		}
		else
		{
		    // try next mode
		    new_dsp_mode++;
		}
	}


	switch (ts.dsp.mode)
	{

	case DSP_SWITCH_OFF: // switch off everything
		ts.dsp.active =  (ts.dsp.active & ~(DSP_NR_ENABLE|DSP_ANR_ENABLE|DSP_NOTCH_ENABLE|DSP_MNOTCH_ENABLE|DSP_MPEAK_ENABLE));
		ts.enc_two_mode = ENC_TWO_MODE_RF_GAIN;
		break;
	case DSP_SWITCH_NR:
		ts.dsp.active =  DSP_NR_ENABLE | (ts.dsp.active & ~(DSP_ANR_ENABLE|DSP_NOTCH_ENABLE|DSP_MNOTCH_ENABLE|DSP_MPEAK_ENABLE));
		ts.enc_two_mode = ENC_TWO_MODE_NR;
		break;
    case DSP_SWITCH_ANR:
        ts.dsp.active =  DSP_ANR_ENABLE | (ts.dsp.active & ~(DSP_NR_ENABLE|DSP_NOTCH_ENABLE|DSP_MNOTCH_ENABLE|DSP_MPEAK_ENABLE));
        ts.enc_two_mode = ENC_TWO_MODE_RF_GAIN;
        break;
	case DSP_SWITCH_NOTCH:
		ts.dsp.active =  DSP_NOTCH_ENABLE | (ts.dsp.active & ~(DSP_NR_ENABLE|DSP_ANR_ENABLE|DSP_MNOTCH_ENABLE|DSP_MPEAK_ENABLE));
		ts.enc_two_mode = ENC_TWO_MODE_RF_GAIN;
		break;
	case DSP_SWITCH_NR_AND_NOTCH:
		ts.dsp.active =  DSP_NOTCH_ENABLE | DSP_NR_ENABLE | (ts.dsp.active & ~(DSP_ANR_ENABLE|DSP_MNOTCH_ENABLE|DSP_MPEAK_ENABLE));
		ts.enc_two_mode = ENC_TWO_MODE_NR;
		break;
	case DSP_SWITCH_NOTCH_MANUAL:
		ts.dsp.active =  DSP_MNOTCH_ENABLE | (ts.dsp.active & ~(DSP_NR_ENABLE|DSP_ANR_ENABLE|DSP_NOTCH_ENABLE|DSP_MPEAK_ENABLE));
		ts.enc_two_mode = ENC_TWO_MODE_NOTCH_F;
		break;
	case DSP_SWITCH_PEAK_FILTER:
		ts.dsp.active =  DSP_MPEAK_ENABLE | (ts.dsp.active & ~(DSP_NR_ENABLE|DSP_ANR_ENABLE|DSP_NOTCH_ENABLE|DSP_MNOTCH_ENABLE));
		ts.enc_two_mode = ENC_TWO_MODE_PEAK_F;
		break;
	default:
		break;
	}

	ts.dsp.active_toggle = ts.dsp.active;  // save update in "toggle" variable
	// reset DSP NR coefficients
	AudioDriver_SetProcessingChain(ts.dmod_mode, true);        // update DSP/filter settings
	UiDriver_DisplayEncoderTwoMode();         // DSP control is mapped to column 2
}

static void UiAction_ChangeToNextDspMode(void)
{
	if(ts.dmod_mode != DEMOD_FM)	  // allow selection/change of DSP only if NOT in FM
	{
		//
		// I think we should alter this to use a counter
		// What do we want to switch here:
		// NR ON/OFF		ts.dsp.active |= DSP_NR_ENABLE;	 // 	ts.dsp.active &= ~DSP_NR_ENABLE;
	    // ANR ON/OFF		ts.dsp.active |= DSP_ANR_ENABLE;
		// NOTCH ON/OFF		ts.dsp.active |= DSP_NOTCH_ENABLE; // 	ts.dsp.active &= ~DSP_NOTCH_ENABLE;
		// Manual Notch		ts.dsp.active |= DSP_MNOTCH_ENABLE
		// BASS				ts.bass // always "ON", gain ranges from -20 to +20 dB, "OFF" = 0dB
		// TREBLE			ts.treble // always "ON", gain ranges from -20 to +20 dB, "OFF" = 0dB

		UiDriver_UpdateDSPmode(ts.dsp.mode + 1);
	}
}


void UiAction_ChangeSpectrumSize()
{
	ts.menu_var_changed = 1;
    if (ts.spectrum_size == SPECTRUM_BIG)
    {
        ts.spectrum_size = SPECTRUM_NORMAL;
    }
    else
    {
        ts.spectrum_size = SPECTRUM_BIG;
    }

    UiDriver_SpectrumChangeLayoutParameters();
}


void UiAction_ChangeSpectrumZoomLevelDown()
{
//	ts.menu_var_changed = 1;
	if (sd.magnify > MAGNIFY_MIN)
	{
	decr_wrap_uint8(&sd.magnify,MAGNIFY_MIN,MAGNIFY_MAX);
	UiDriver_SpectrumChangeLayoutParameters();
	}
}

void UiAction_ChangeSpectrumZoomLevelUp()
{
//	ts.menu_var_changed = 1;
	if (sd.magnify < MAGNIFY_MAX)
	{
	incr_wrap_uint8(&sd.magnify,MAGNIFY_MIN,MAGNIFY_MAX);
	UiDriver_SpectrumChangeLayoutParameters();
	}
}

void UiAction_ChangeFrequencyToNextKhz()
{
	df.tune_new = floor(df.tune_new / 1000) * 1000;	// set last three digits to "0"
	UiDriver_FrequencyUpdateLOandDisplay(true);
}

void UiAction_ToggleWaterfallScopeDisplay()
{
    SpectrumMode_t temp = UiDriver_GetSpectrumMode();

    if (temp != SPECTRUM_BLANK)
    {
        // we want range 0 - 2 instead of the normal 1 - 3
        temp--;
    }
    temp++;
    temp%=3;
    temp++;
    UiDriver_SetSpectrumMode(temp);
    UiSpectrum_ResetSpectrum();
    UiSpectrum_Init();   // init spectrum display
}

void UiAction_ChangeDemodMode()
{
	if((!ts.tune) && (ts.txrx_mode == TRX_MODE_RX))	 	// do NOT allow mode change in TUNE mode or transmit mode
	{
        UiDriver_ChangeToNextDemodMode(0);
        UiDriver_HandlePowerLevelChange(ts.band_effective, ts.power_level);
	}
}
//static void UiAction_ChangeDemodModeToAlternativeMode()
void UiAction_ChangeDemodModeToAlternativeMode()
{
	if((!ts.tune) && (ts.txrx_mode == TRX_MODE_RX))	 	// do NOT allow mode change in TUNE mode or transmit mode
	{
		UiDriver_ChangeToNextDemodMode(1);
	}
}

void UiAction_ChangePowerLevel()
{
    uint8_t pl = ts.power_level;
    incr_wrap_uint8(&pl,0,mchf_power_levelsInfo.count-1);
	UiDriver_HandlePowerLevelChange(ts.band_effective, pl);

}

void UiAction_ChangePowerLevelToFull()
{
	UiDriver_HandlePowerLevelChange(ts.band_effective, 0);
}

void UiAction_ChangeAudioSource()
{
	if(ts.dmod_mode != DEMOD_CW)
	{
		incr_wrap_uint8(&ts.tx_audio_source,0,TX_AUDIO_MAX_ITEMS);

		UiDriver_DisplayEncoderThreeMode();
	}
}

// TODO: Decide if we really want to switch
// order like for the normal buttons
void UiAction_ChangeBandDownOrUp()
{
//	UiDriver_HandleBandButtons(BUTTON_BNDM);
	if(ts.expflags1 & EXPFLAGS1_BANDCH_JUMP_SWAP)
	{
	    bool buttondirSwap = (ts.flags1 & FLAGS1_SWAP_BAND_BTN)?true:false;
	    if (!buttondirSwap) { df.tune_new -= 48000 / (1 << sd.magnify); }
	    else { df.tune_new += 48000 / (1 << sd.magnify); }
	}
	else
	{
		UiDriver_HandleBandButtons(BUTTON_BNDM);
	}
}

void UiAction_ChangeBandUpOrDown()
{
//	UiDriver_HandleBandButtons(BUTTON_BNDP);
	if(ts.expflags1 & EXPFLAGS1_BANDCH_JUMP_SWAP)
	{
	    bool buttondirSwap = (ts.flags1 & FLAGS1_SWAP_BAND_BTN)?true:false;
	    if (!buttondirSwap) { df.tune_new += 48000 / (1 << sd.magnify); }
	    else { df.tune_new -= 48000 / (1 << sd.magnify); }
	}
	else
	{
		UiDriver_HandleBandButtons(BUTTON_BNDP);
	}
}

static void UiAction_SaveConfigurationToMemory(void)
{
	if(ts.txrx_mode == TRX_MODE_RX)	 				// only allow EEPROM write in receive mode
//	{
//		UiDriver_DisplayMessageStart();
//		UiDriver_SaveConfiguration();
//		HAL_Delay(3000);
//        UiDriver_DisplayMessageStop();
//
//		ts.menu_var_changed = 0;                    // clear "EEPROM SAVE IS NECESSARY" indicators
//		UiDriver_DisplayFButton_F1MenuExit();
//	}
	{
	    if(ts.menu_var_changed != 0)
        {
            UiDriver_DisplayMessageStart();
            UiDriver_SaveConfiguration();
            HAL_Delay(3000);
            UiDriver_DisplayMessageStop();

            if((ts.menu_mode))              // update menu when we are (or WERE) in MENU mode
            {
                UiSpectrum_Clear();
        #ifndef SDR_AMBER_480_320
            #ifndef OVI40_MOD_480_320
                if(ts.show_wide_spectrum) { UiLcdHy28_PrintTextCentered(0, 175, 79," SETUP  ",Blue,Black,0); }
            #else
                UiLcdHy28_PrintTextCentered(0, 205, 130," SETUP  ",Blue,Black,0);
            #endif
        #else
                UiLcdHy28_PrintTextCentered(0, 205, 130," SETUP  ",Blue,Black,0);
        #endif
                UiMenu_RenderMenu(MENU_RENDER_ONLY);
            }

			if(!ts.show_wide_spectrum)
			{
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
				// Do nothing
	#else
//				UiLcdHy28_PrintTextCentered(0, 205, 130," SETUP  ",Blue,Black,0);
	#endif
#else
//				UiLcdHy28_PrintTextCentered(0, 205, 130," SETUP  ",Blue,Black,0);
#endif
			}
			else if(ts.menu_mode)
			{
				UiLcdHy28_PrintTextCentered(0, 175, 79," SETUP  ",Blue,Black,0);
                UiMenu_RenderMenu(MENU_RENDER_ONLY);
			}
            ts.menu_var_changed = 0;                    // clear "EEPROM SAVE IS NECESSARY" indicators
            UiDriver_DisplayFButton_F1MenuExit();
        }
	    else if(ts.Layout->Size.x == 320) // only for 320x240 screen
	    { // Toggle Spectrum full wide
	        if(!ts.show_wide_spectrum)
	        {
	        	ts.show_wide_spectrum = true;
//	        	UiLcdHy28_PrintText(ts.Layout->DEBUG_X,ts.Layout->LOADANDDEBUG_Y,"WideSpec",Green,Black,0);// Debug
	        }
	        else
	        {
	        	ts.show_wide_spectrum = false;
//	        	UiLcdHy28_PrintText(ts.Layout->DEBUG_X,ts.Layout->LOADANDDEBUG_Y,"         ",Green,Black,0);// Debug
	        }
	        UiDriver_ToggleWideSpectrum();
	    }
	}
}

static void UiDriver_DrawGraticule_Rect(bool show)
{
	uint16_t pos_y=sd.Slayout->graticule.y;
	if(show)
	{

		UiLcdHy28_PrintText(sd.Slayout->graticule.x+5,pos_y+4,"CHOOSE NEW POSITION",White,Black,4);
		UiLcdHy28_PrintText(sd.Slayout->graticule.x+sd.Slayout->graticule.w-65,pos_y+4,"| OK |",Green,Black,4);
		UiLcdHy28_PrintText(sd.Slayout->graticule.x+sd.Slayout->graticule.w-20,pos_y+4,"X",Orange,Black,4);
		UiLcdHy28_DrawEmptyRect(sd.Slayout->graticule.x+1,pos_y+1,sd.Slayout->graticule.h-3,sd.Slayout->graticule.w-3,White);
	}
	else
	{
		//clear the graticule area
		UiLcdHy28_DrawFullRect(sd.Slayout->graticule.x+1,pos_y+1,sd.Slayout->graticule.h-2,sd.Slayout->graticule.w-2,Black);
	}
}

/*
 * Special actions for long pressed spectrum/waterfall area
 */
void UiAction_CheckSpectrumTouchActions()
{
	//UiArea_t* ScGRAT_area=UiSpectrum_GetSpectrumGraticule();	//fetch the current scope graticule area pointer

	if(UiDriver_CheckTouchRegion(&sd.Slayout->graticule)			//check for spectrum/waterfall size ratio change
			&&ts.txrx_mode == TRX_MODE_RX)
	{
		if(ts.SpectrumResize_flag==0)
		{
			UiSpectrum_Clear();
			UiDriver_DrawGraticule_Rect(true);		//draw new graticule control
			UiLcdHy28_DrawEmptyRect(sd.Slayout->full.x,sd.Slayout->full.y,sd.Slayout->full.h-1,sd.Slayout->full.w-1,White);
			ts.SpectrumResize_flag=1;
			return;
		}
	}
}

void UiAction_ZoomResetToOne()
{
	sd.magnify = MAGNIFY_MIN;
	UiDriver_SpectrumChangeLayoutParameters();
}

//SpectrumVirtualKeys_flag

void UiAction_ChangeFrequencyByTouch()
{
	if (ts.frequency_lock == false)
	{
		int step = 500;				// adjust to 500Hz

		if(sd.magnify == 3)
		{
			step = 100;					// adjust to 100Hz
		}
		if(sd.magnify == 4)
		{
			step = 10;					// adjust to 10Hz
		}
		if(sd.magnify == 5)
		{
			step = 1;					// adjust to 1Hz
		}
		if(ts.dmod_mode == DEMOD_AM || ts.dmod_mode == DEMOD_SAM)
		{
			step = 5000;				// adjust to 5KHz
		}

		//int16_t line =sd.marker_pos[0] + UiSpectrum_GetSpectrumStartX();
		int16_t line =sd.marker_pos[0] + sd.Slayout->scope.x;

		/*int16_t line =sd.rx_carrier_pos;
		if(ts.dmod_mode == DEMOD_CW)
		{
			line=sd.marker_pos[0];
		}*/

		//uint32_t tunediff = ((IQ_SAMPLE_RATE/slayout.scope.w)/(1 << sd.magnify))*(ts.tp->hr_x-line);
		int32_t tunediff = sd.hz_per_pixel*(ts.tp->hr_x-line);
		df.tune_new = lround((df.tune_new + tunediff)/step) * step;
		UiDriver_FrequencyUpdateLOandDisplay(true);
	}
}

void UiAction_ChangeDigitalMode()
{
//    if(ts.show_wide_spectrum)
//    {
//        return;
//    }
	incr_wrap_uint8(&ts.digital_mode,0,DigitalMode_Num_Modes-1);
	// We limit the reachable modes to the ones truly available
	// which is FreeDV1, RTTY, BPSK for now
	UiDriver_ToggleDigitalMode();
}

void UiAction_ChangeDynamicTuning()
{
	if (!(ts.flags1 & FLAGS1_DYN_TUNE_ENABLE))			// is it off??
	{
		ts.flags1 |= FLAGS1_DYN_TUNE_ENABLE;	// then turn it on
	}
	else
	{
		ts.flags1 &= ~FLAGS1_DYN_TUNE_ENABLE;	// then turn it off
	}

	UiDriver_DisplayFreqStepSize();
}

void UiAction_ChangeRXInputStateDown()
{
#ifdef SDR_AMBER
	if (ts.amber_input_state < 3)
	{
		ts.amber_input_state ++;
		if(ts.amber_io8_present)
		{
		    Board_Amber_InputStateSet(ts.amber_input_state);
		}
	}
#endif
}

void UiAction_ChangeRXInputStateUp()
{
#ifdef SDR_AMBER
	if (ts.amber_input_state > 0)
	{
		ts.amber_input_state --;
        if(ts.amber_io8_present)
        {
            Board_Amber_InputStateSet(ts.amber_input_state);
        }
	}
#endif
}

void UiAction_ChangeDebugInfoDisplay()
{
	UiDriver_DebugInfo_DisplayEnable(!ts.show_debug_info);
}

static void UiAction_ChangeFilterBW(void)
{
	if(!ts.tune)
	{
		if (filter_path_change == true)
		{
			filter_path_change = false;
		}
		else
		{
			if (ts.expflags2 & EXPFLAGS2_TEMP_MEM_BW_MOD_BAND) // enabled temp. remember BW on mode & band
			{
				UiDriver_StoreBwData();
			}

			AudioFilter_NextApplicableFilterPath(PATH_USE_RULES,AudioFilter_GetFilterModeFromDemodMode(ts.dmod_mode),ts.filter_path);
			// we store the new filter in the current active filter location
			AudioDriver_SetProcessingChain(ts.dmod_mode, false);
			// we activate it (in fact the last used one, which is the newly selected one);
		}
		// Change filter
		UiDriver_UpdateDisplayAfterParamChange();		// re-init for change of filter including display updates
		UiDriver_DisplayEncoderThreeMode();
	}
}

static void UiAction_ChangeTuningStepDownOrUp(void)
{
	UiDriver_ChangeTuningStep(ts.freq_step_config & FREQ_STEP_SWAP_BTN? true: false);
}

static void UiAction_ChangeTuningStepUpOrDown(void)
{
	UiDriver_ChangeTuningStep(ts.freq_step_config & FREQ_STEP_SWAP_BTN? false: true);
}

static void UiAction_ChangeBacklightBrightness(void)
{
	incr_wrap_uint8(&ts.lcd_backlight_brightness,LCD_DIMMING_LEVEL_MIN,LCD_DIMMING_LEVEL_MAX);
}

static void UiAction_ToggleTxDisable(void)
{
	if(ts.txrx_mode == TRX_MODE_RX)			// do NOT allow mode change in TUNE mode or transmit mode
	{
		if( RadioManagement_IsTxDisabledBy(TX_DISABLE_USER) == false)
		{
			ts.tx_disable |= TX_DISABLE_USER;
		}
		else
		{
			ts.tx_disable &= ~TX_DISABLE_USER;
		}
		UiDriver_FButton_F5Tune();
		UiDriver_DisplayPowerLevel();
	}
}

static void UiAction_ToggleVfoMem(void)
{
	RadioManagement_ToggleVfoMem();
	UiDriver_FButton_F3MemSplit();
	if(ts.mem_disp)
	{
	    ts.mem_disp = false;
	    UiSpectrum_Init();
	}
}

static void UiAction_GoToMainFmenu(void)
{
	if(ts.ExtFMenuActive)
	{
		ts.ExtFMenuActive = false;
		UiDriver_CreateFunctionButtons(false);
	}
}

static void UiAction_ToggleDspEnable(void)
{
	if(ts.dmod_mode != DEMOD_FM)	 		// do not allow change of mode when in FM
	{
		if(is_dsp_nr() || is_dsp_anr() || is_dsp_notch() || is_dsp_mnotch() || is_dsp_mpeak())   // is any DSP function active?
		{
			ts.dsp.active_toggle = ts.dsp.active;	// save setting for future toggling

			ts.dsp.active &= ~(DSP_NR_ENABLE | DSP_ANR_ENABLE | DSP_NOTCH_ENABLE |DSP_MNOTCH_ENABLE | DSP_MPEAK_ENABLE); // turn off DSP

			ts.enc_two_mode = ENC_TWO_MODE_RF_GAIN;
		}
		else	 		// neither notch or NR was active
		{
			if(ts.dsp.active_toggle != 0xff)	 	// has this holder been used before?
			{
				ts.dsp.active = ts.dsp.active_toggle;	// yes - load value
			}
		}
		AudioDriver_SetProcessingChain(ts.dmod_mode, false);	// update DSP settings
		UiDriver_DisplayEncoderTwoMode();
	}
}

// TODO: Split into separate actions and a composition
static void UiAction_ChangeRxFilterOrFmToneBurst(void)
{
	if((!ts.tune) && (ts.txrx_mode == TRX_MODE_RX))	  // only allow in receive mode and when NOT in FM
	{
		filter_path_change = true;
		UiDriver_DisplayFilter();
		UiDriver_DisplayEncoderThreeMode();
	}
	else if((ts.txrx_mode == TRX_MODE_TX) && (ts.dmod_mode == DEMOD_FM))
	{
		if(ts.fm_tone_burst_mode != FM_TONE_BURST_OFF)	 	// is tone burst mode enabled?
		{
			ads.fm_conf.tone_burst_active = 1;					// activate the tone burst
			ts.fm_tone_burst_timing = ts.sysclock + FM_TONE_BURST_DURATION;	// set the duration/timing of the tone burst
		}
	}
}

static void UiAction_ToggleNoiseblanker(void)
{
	ts.dsp.active ^= DSP_NB_ENABLE;	// toggle whether or not DSP or NB is to be displayed
	UiDriver_DisplayEncoderTwoMode();
}

static void UiAction_ToggleTuneMode(void)
{
	ts.tune = RadioManagement_Tune(!ts.tune);
	UiDriver_DisplayPowerLevel();           // tuning may change power level temporarily
	UiDriver_FButton_F5Tune();
}

static void UiAction_PlayKeyerBtnN(int8_t n)
{
	uint8_t* pmacro;
	uint16_t c = 0;

	if (ts.keyer_mode.button_recording == KEYER_BUTTON_NONE)
	{
		pmacro = ( uint8_t* )ts.keyer_mode.macro[n];
		if (*pmacro != '\0') // If there is a macro
		{
		    if((ts.expflags1 & EXPFLAGS1_FAST_MACRO_1_2) && ((n == KEYER_BUTTON_1) || (n == KEYER_BUTTON_2)))
		    {
		        ts.cw_keyer_speed_bak = ts.cw_keyer_speed;
		        ts.cw_keyer_speed = CW_KEYER_SPEED_MAX;
		        CwGen_SetSpeed();
		    }
		    if(ts.keyer_mode_tx == CYCLED || ts.keyer_mode_tx == BEACON)
		    {
                DigiModes_TxBufferReset();
		    }
			while (*pmacro != '\0')
			{
				DigiModes_TxBufferPutChar( *pmacro++, UI );
			}
//			if (ts.buffered_tx)
			if (ts.keyer_mode_tx == TXRX_B)
			{
			    DigiModes_TxBufferPutChar( 0x04, UI );
			    // we put an EOT in the buffer, which tells the respective consumer
			    // to return to receive after transmitting all characters before this
			}

			if ((ts.dmod_mode == DEMOD_CW
			        && ts.cw_keyer_mode != CW_KEYER_MODE_STRAIGHT)
			        || is_demod_psk()
			        || is_demod_rtty())
			{
                if(ts.keyer_mode_tx == CYCLED || ts.keyer_mode_tx == BEACON)
                {
                    ts.keyer_cycled_tx_active = true;
                    ts.keyer_cycled_tx_button = n;
                }
                if(ts.keyer_mode_tx == BEACON)
                {
                    UiDriver_TextMsgClear();
                }
				RadioManagement_Request_TxOn();
			}
		}
		if(!(ts.dmod_mode == DEMOD_CW && !ts.cw_decoder_enable))
		{
		    UiDriver_TextMsgPutChar('>');
		}
	}
	else if (ts.keyer_mode.button_recording == n)
	{
		ts.cw_text_entry = false; // FIXME looks like we can remove cw_text_entry
		pmacro = (uint8_t *)ts.keyer_mode.macro[n];
		c = 0;
		/*
		 * Kind of trick. It will not check second condition if first is the false,
		 * so it keeps pointer to the last available element in array for macro
		 * to put there terminator
		 */
		while (( ++c <= KEYER_MACRO_LEN - 1 ) && DigiModes_TxBufferRemove( pmacro, UI ))
		{
		    pmacro++;
		}
		*pmacro = '\0';

		// strip out the spaces from the end of line
		while(( pmacro != ts.keyer_mode.macro[n] ) && *--pmacro == ' ' )
		{
			*pmacro = '\0';
		}

		UiConfiguration_UpdateMacroCap();
		if(!(ts.dmod_mode == DEMOD_CW && !ts.cw_decoder_enable))
		{
		    UiDriver_TextMsgPutChar('<');
		}
		ts.keyer_mode.button_recording = KEYER_BUTTON_NONE;

		// setup back the previous consumer of digi buffer
		// as UI has higher priority we can be sure that
		// it's not changed by changing modes
		DigiModes_Restore_BufferConsumer();
	}
	UiDriver_CreateFunctionButtons( false );
}

static void UiAction_PlayKeyerBtn1(void)
{
	UiAction_PlayKeyerBtnN(KEYER_BUTTON_1);
}

static void UiAction_PlayKeyerBtn2(void)
{
	UiAction_PlayKeyerBtnN(KEYER_BUTTON_2);
}

static void UiAction_PlayKeyerBtn3(void)
{
	UiAction_PlayKeyerBtnN(KEYER_BUTTON_3);
}

static void UiAction_PlayKeyerBtn4(void)
{
    UiAction_PlayKeyerBtnN(KEYER_BUTTON_4);
}

static void UiAction_RecordKeyerBtnN(int8_t n)
{
	if (ts.keyer_mode.button_recording == KEYER_BUTTON_NONE
	        && ts.txrx_mode == TRX_MODE_RX
	        && !ts.cw_text_entry
	        && ( ts.dmod_mode == DEMOD_CW
	        || is_demod_psk()
	        || is_demod_rtty()))
	{
		ts.cw_text_entry = true; // FIXME can be removed later

		ts.keyer_mode.button_recording = n;
		DigiModes_Set_BufferConsumer( UI );
		DigiModes_TxBufferReset();
		if(!(ts.dmod_mode == DEMOD_CW && !ts.cw_decoder_enable))
		{
		    UiDriver_TextMsgPutChar(':');
		}
		UiDriver_CreateFunctionButtons(false);
	}
}

static void UiAction_RecordKeyerBtn1(void)
{
	UiAction_RecordKeyerBtnN(KEYER_BUTTON_1);
}

static void UiAction_RecordKeyerBtn2(void)
{
	UiAction_RecordKeyerBtnN(KEYER_BUTTON_2);
}

static void UiAction_RecordKeyerBtn3(void)
{
	UiAction_RecordKeyerBtnN(KEYER_BUTTON_3);
}

static void UiAction_RecordKeyerBtn4(void)
{
    UiAction_RecordKeyerBtnN(KEYER_BUTTON_4);
}

static void UiAction_ToggleBufferedTXMode(void)
{
//	ts.buffered_tx = ! ts.buffered_tx;
	switch(ts.keyer_mode_tx)
	{
	case TXRX_U:
		ts.keyer_mode_tx = TXRX_B;
		break;
	case TXRX_B:
		ts.keyer_mode_tx = CYCLED;
		break;
	case CYCLED:
		ts.keyer_mode_tx = BEACON;
		break;
	case BEACON:
		ts.keyer_mode_tx = TXRX_U;
		ts.keyer_cycled_tx_active = false;
		break;
	}
	UiDriver_FButton_F5Tune();
}

static void UiAction_ToggleTxRx(void)
{
    ts.keyer_cycled_tx_active = false;
	if(ts.txrx_mode == TRX_MODE_RX)
	{
	    RadioManagement_Request_TxOn();
	}
	else
	{
	    RadioManagement_Request_TxOff();
        if(ts.keyer_mode_tx == CYCLED || ts.keyer_mode_tx == BEACON)
        {
        DigiModes_TxBufferReset();
        }
        if(ts.cw_keyer_speed != ts.cw_keyer_speed_bak)
        {
            ts.cw_keyer_speed = ts.cw_keyer_speed_bak;
            CwGen_SetSpeed();
        }
	}
	UiDriver_FButton_F5Tune();
}

static void UiDriver_CheckCycledKeyerTX(void)
{
    if(ts.keyer_cycled_tx_active)
    {
        if (ts.txrx_mode == TRX_MODE_TX)
        {
            ts.keyer_cycled_tx_timer = ts.sysclock + (ts.keyer_mode_tx == BEACON? ts.cw_beacon_interval: 100 * ts.keyer_cycled_interval); // by menu options
        }
        else // RX mode
        {
            if(ts.keyer_cycled_tx_timer < ts.sysclock)
            {
                UiAction_PlayKeyerBtnN(ts.keyer_cycled_tx_button);
                ts.keyer_cycled_tx_timer = ts.sysclock + 50; // for prevent double TX-ing of macro
            }
        }
    }
}

static void UiAction_ToggleSplitModeOrToggleMemMode(void)
{
	if(!ts.vfo_mem_flag)	 		// update screen if in VFO (not memory) mode
	{
		UiDriver_SetSplitMode(!is_splitmode());
	}
	else	 		// in memory mode
	{
		UiSpectrum_Clear();		// always clear display
		ts.mem_disp = !ts.mem_disp;
		if (ts.mem_disp == 0 )
		{
			UiSpectrum_Init();	// init spectrum scope
		}
		else
		{
//		    UiAction_ShowMems();
			if(ts.expflags2 & EXPFLAGS2_CONTEST_MODE_F3_ON)
			{
				UiAction_ShowCq();
			}
			else
			{
				UiAction_ShowMems();
			}
		}
	}
}

static void UiAction_ToggleMenuMode(void)
{
	if(!ts.mem_disp)	 			// allow only if NOT in memory display mode
	{
		if(ts.menu_mode == false)	 	// go into menu mode if NOT already in menu mode and not to halt on startup
		{
			ts.menu_mode = true;
			ts.encoder3state = filter_path_change;
			filter_path_change = false;			// deactivate while in menu mode
			UiDriver_DisplayFilter();
			UiSpectrum_Clear();
			if(!ts.show_wide_spectrum)
			{
#ifndef SDR_AMBER_480_320
	#ifndef OVI40_MOD_480_320
				// Do nothing
	#else
				UiLcdHy28_PrintTextCentered(0, 205, 130," SETUP  ",Blue,Black,0);
	#endif
#else
				UiLcdHy28_PrintTextCentered(0, 205, 130," SETUP  ",Blue,Black,0);
#endif
			}
			else
			{
				UiLcdHy28_PrintTextCentered(0, 175, 79," SETUP  ",Blue,Black,0);
			}
			UiDriver_DisplayFButton_F1MenuExit();
			UiDriver_DrawFButtonLabel(2,"PREV",Yellow);
			UiDriver_DrawFButtonLabel(3,"NEXT",Yellow);
			UiDriver_DrawFButtonLabel(4,"DEFLT",Yellow);
			//
			//
			// Grey out adjustments and put encoders in known states
			//
			UiDriver_RefreshEncoderDisplay();

			ts.menu_var = 0;
			//
			UiMenu_RenderMenu(MENU_RENDER_ONLY);	// Draw the menu the first time
			UiMenu_RenderMenu(MENU_PROCESS_VALUE_CHANGE);	// Do update of the first menu item
		}
		else	 	// already in menu mode - we now exit
		{
			ts.menu_mode = false;
			filter_path_change = ts.encoder3state;
			UiDriver_DisplayFilter();
			UiSpectrum_Init();			// init spectrum scope

			// Restore encoder displays to previous modes
			UiDriver_RefreshEncoderDisplay();
			UiDriver_DisplayFilter();	// update bandwidth display
			UiDriver_CreateFunctionButtons(false);
		}
	}
}

static void UiAction_ToggleKeyerMode(void)
{
//    if(ts.vfo_mem_flag) // wrong hole!
//    {
//        return;
//    }
//	ts.keyer_mode.active = !ts.keyer_mode.active;

    if(ts.menu_mode)
    {
        return;
    }

    if(ts.mem_disp || ts.xvtr_disp)
    {
        ts.mem_disp = false;
        ts.xvtr_disp = false;
        UiSpectrum_Clear();
        UiSpectrum_Init();
    }

    if(ts.expflags2 & EXPFLAGS2_FMENUS_SELECT)
    {
        ts.keyer_mode.active = false;
        ts.ExtFMenuActive = false;
        ts.vfo_mem_flag = false;
        ts.XvtrFMenuActive = false;
        ts.MenuOfFMenusActive = true;
    }
    else if(!ts.keyer_mode.active && !ts.ExtFMenuActive && !ts.vfo_mem_flag && !ts.XvtrFMenuActive)
    {
        ts.MenuOfFMenusActive = false;
    	ts.keyer_mode.active = true;
    }
    else if(ts.keyer_mode.active && !ts.ExtFMenuActive && !ts.vfo_mem_flag && !ts.XvtrFMenuActive)
    {
        ts.MenuOfFMenusActive = false;
    	ts.keyer_mode.active = false;
    	ts.ExtFMenuActive = true;
    }
    else if(!ts.keyer_mode.active && ts.ExtFMenuActive && !ts.vfo_mem_flag && !ts.XvtrFMenuActive)
    {
        ts.MenuOfFMenusActive = false;
    	ts.ExtFMenuActive = false;
    	ts.vfo_mem_flag = true;
    }
    else if(!ts.keyer_mode.active && !ts.ExtFMenuActive && ts.vfo_mem_flag && !ts.XvtrFMenuActive)
    {
        ts.MenuOfFMenusActive = false;
    	ts.vfo_mem_flag = false;
    	ts.XvtrFMenuActive = true;
    }
    else if(!ts.keyer_mode.active && !ts.ExtFMenuActive && !ts.vfo_mem_flag && ts.XvtrFMenuActive)
    {
        ts.MenuOfFMenusActive = false;
    	ts.XvtrFMenuActive = false;
    }
    else // miracle!
    {
        ts.MenuOfFMenusActive = false;
    	ts.keyer_mode.active = false;
    	ts.ExtFMenuActive = false;
    	ts.vfo_mem_flag = false;
    }
	ts.keyer_cycled_tx_active = false;
	UiDriver_CreateFunctionButtons(false);
}

static void UiAction_BandMinusHold(void)
{
	if(UiDriver_IsButtonPressed(BUTTON_PWR_PRESSED))	 	// and POWER button pressed-and-held at the same time?
	{
		UiDriver_LcdBlankingStealthSwitch();
	}
	else if(UiDriver_IsButtonPressed(BUTTON_BNDP_PRESSED))	 	// and BAND-UP pressed at the same time?
	{
		if(!ts.menu_mode)	 			// do not do this in menu mode!
		{
			UiAction_ToggleWaterfallScopeDisplay();
		}
	}
	else                                                    //skip freq -48kHz/magnify - for quick scan of band
    {
//        if(ts.txrx_mode == TRX_MODE_RX)
//        {
//            df.tune_new -= 48000 / (1 << sd.magnify);
//        }
		if(ts.expflags1 & EXPFLAGS1_BANDCH_JUMP_SWAP)
		{
			UiDriver_HandleBandButtons(BUTTON_BNDM);
		}
		else
		{
		    bool buttondirSwap = (ts.flags1 & FLAGS1_SWAP_BAND_BTN)?true:false;
		    if (!buttondirSwap) { df.tune_new -= 48000 / (1 << sd.magnify); }
		    else { df.tune_new += 48000 / (1 << sd.magnify); }
		}
    }
}

static void UiAction_BandPlusHold(void)
{
	if(UiDriver_IsButtonPressed(BUTTON_BNDM_PRESSED))	 	// and BAND-DOWN pressed at the same time?
	{
		if(!ts.menu_mode)	 		// do not do this if in menu mode!
		{
			UiAction_ToggleWaterfallScopeDisplay();
		}
	}
	else if(UiDriver_IsButtonPressed(BUTTON_PWR_PRESSED))	 	// and POWER button pressed-and-held at the same time?
	{
		UiDriver_PowerDownCleanup(false); // do not save the configuration
	}
	else                                                    //skip freq +48kHz/magnify - for quick scan of band
    {
//        if(ts.txrx_mode == TRX_MODE_RX)
//        {
//            df.tune_new += 48000 / (1 << sd.magnify);
//        }
		if(ts.expflags1 & EXPFLAGS1_BANDCH_JUMP_SWAP)
		{
			UiDriver_HandleBandButtons(BUTTON_BNDP);
		}
		else
		{
		    bool buttondirSwap = (ts.flags1 & FLAGS1_SWAP_BAND_BTN)?true:false;
		    if (!buttondirSwap) { df.tune_new += 48000 / (1 << sd.magnify); }
		    else { df.tune_new -= 48000 / (1 << sd.magnify); }
		}
    }
}

static void UiAction_PowerHold(void)
{
	if(UiDriver_IsButtonPressed(BUTTON_BNDM_PRESSED))	 	// was keyPin BAND- pressed at the same time?
	{
		UiDriver_LcdBlankingStealthSwitch();
	}
	else
	{
		// ONLY the POWER button was pressed
		if(ts.txrx_mode == TRX_MODE_RX)  		// only allow power-off in RX mode
		{
			UiDriver_PowerDownCleanup(true);
		}
	}
}

static void UiAction_StepMinusHold(void)
{
	if(UiDriver_IsButtonPressed(BUTTON_STEPP_PRESSED))	 	// was keyPin STEP+ pressed at the same time?
	{
		ts.frequency_lock = !ts.frequency_lock;

        if(ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE)
        {
            ts.iq_freq_delta = 0;
        }
		// update frequency display
		UiDriver_FrequencyUpdateLOandDisplay(true);
	}
	else
	{
		if(!(ts.freq_step_config & FREQ_STEP_SWAP_BTN))	  // keyPin swap NOT enabled
		{
			UiDriver_PressHoldStep(0);	// decrease step size
		}
		else  		// keyPin swap enabled
		{
			UiDriver_PressHoldStep(1);	// increase step size
		}
	}
}

static void UiAction_MenuSetDefaultValue(void)
{
	UiMenu_RenderMenu(MENU_PROCESS_VALUE_SETDEFAULT);
}

static void UiAction_MenuRenderPrevScreen(void)
{
    UiMenu_RenderPrevScreen();
}

static void UiAction_MenuRenderNextScreen(void)
{
    UiMenu_RenderNextScreen();
}

static void UiAction_StepPlusHold(void)
{
	if(UiDriver_IsButtonPressed(BUTTON_STEPM_PRESSED))	 	// was keyPin STEP- pressed at the same time?
	{
		ts.frequency_lock = !ts.frequency_lock;

		if(ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE)
		{
		    ts.iq_freq_delta = 0;
		}
		// update frequency display
		UiDriver_FrequencyUpdateLOandDisplay(true);
	}
	else
	{
		if(!(ts.freq_step_config & FREQ_STEP_SWAP_BTN))  	// keyPin swap NOT enabled
		{
			UiDriver_PressHoldStep(1);	// increase step size
		}
		else  		// keyPin swap enabled
		{
			UiDriver_PressHoldStep(0);	// decrease step size
		}
	}
}

static bool UiDriver_Process_WFscope_RatioChange(void)
{
	bool TouchProcessed=false;

	UiArea_t OKArea;
	OKArea.x=sd.Slayout->graticule.x+sd.Slayout->graticule.w-65;
	OKArea.y=sd.Slayout->graticule.y;
	OKArea.h=sd.Slayout->graticule.h;
	OKArea.w=25;
	UiArea_t ExitArea;
	ExitArea.x=sd.Slayout->graticule.x+sd.Slayout->graticule.w-30;
	ExitArea.y=sd.Slayout->graticule.y;
	ExitArea.h=sd.Slayout->graticule.h;
	ExitArea.w=30;

	if(UiDriver_CheckTouchRegion(&OKArea))
	{
		ts.SpectrumResize_flag=0;
		ts.graticulePowerupYpos=sd.Slayout->graticule.y;		//store current graticule position for future eeprom save
		UiDriver_DisplayFButton_F1MenuExit();		//redraw the menu button to indicate the changed item
		ts.menu_var_changed=1;
		ts.flags1 |= FLAGS1_SCOPE_ENABLED;
		ts.flags1 |= FLAGS1_WFALL_ENABLED;
		UiSpectrum_Init();
		TouchProcessed=true;
	}
	else if(UiDriver_CheckTouchRegion(&ExitArea))
	{
		ts.SpectrumResize_flag=0;
		UiSpectrum_Init();
		TouchProcessed=true;
	}
	else if(UiDriver_CheckTouchRegion(&sd.Slayout->full))
	{
		UiDriver_DrawGraticule_Rect(false); 	 //clear graticule current area
		sd.Slayout->graticule.y=UiSprectrum_CheckNewGraticulePos(ts.tp->hr_y);
		UiDriver_DrawGraticule_Rect(true);		//draw new graticule control
		TouchProcessed=true;
	}

	return TouchProcessed;
}
#define TOUCH_SHOW_REGIONS_AND_POINTS	//this definition enables the drawing of boxes of regions and put the pixel in touch point

#ifdef SDR_AMBER
static void UiAction_ToggleIllumButt(void)
{
    if(ts.amber_io8_present)
    {
        // bit6 == 1 - Illumination of buttons is ON
        if(ts.expflags2 & EXPFLAGS2_ILLUM_BUTT)
        {

            ts.amber_io8_state = ts.amber_io8_state & ~64; // reset bit
            ts.expflags2 = ts.expflags2 & ~EXPFLAGS2_ILLUM_BUTT; // reset flag
        }
        else
        {
            ts.amber_io8_state = ts.amber_io8_state | 64; // set bit
            ts.expflags2 = ts.expflags2 | EXPFLAGS2_ILLUM_BUTT; // set flag
        }
        Board_AmberIOx8_Write(ts.amber_io8_state);
    }
}
#endif

static void UiDriver_HandleTouchScreen(bool is_long_press)
{
	if(!(ts.disabled_tp) && is_touchscreen_pressed())
	{
		uint32_t touchaction_idx = ts.menu_mode == true?1:0;

		if (ts.show_debug_info)					// show coordinates for coding purposes
		{
			char text[14];
			snprintf(text,14,"%04d%s%04d%s",ts.tp->hr_x," : ",ts.tp->hr_y,"  ");

    #ifdef TOUCH_SHOW_REGIONS_AND_POINTS
			UiLcdHy28_DrawColorPoint(ts.tp->hr_x,ts.tp->hr_y,White);

			uint16_t x,y,w,h;
			for(int n=0;n<ts.Layout->touchaction_list[touchaction_idx].size;n++)
			{
				x=ts.Layout->touchaction_list[touchaction_idx].actions[n].region.x;
				y=ts.Layout->touchaction_list[touchaction_idx].actions[n].region.y;
				w=ts.Layout->touchaction_list[touchaction_idx].actions[n].region.w;
				h=ts.Layout->touchaction_list[touchaction_idx].actions[n].region.h;
				UiLcdHy28_DrawEmptyRect(x,y,h,w,Red);
			}
    #endif


			UiLcdHy28_PrintText(0,ts.Layout->LOADANDDEBUG_Y,text,White,Black,0);
		}

		bool TouchProcessed=0;
		if(ts.SpectrumResize_flag==true
				&& ts.menu_mode==0)
		{
			TouchProcessed=UiDriver_Process_WFscope_RatioChange();
		}
		else if(ts.VirtualKeysShown_flag)
		{
			TouchProcessed=UiVk_Process_VirtualKeypad(is_long_press);
		}

		if(!TouchProcessed)
		{
			UiDriver_ProcessTouchActions(&ts.Layout->touchaction_list[touchaction_idx], is_long_press);
		}

		ts.tp->state = TP_DATASETS_PROCESSED;							// set statemachine to data fetched
	}
}

static void UiDriver_HandleTouchScreenShortPress(bool is_long_press)
{
    UiDriver_HandleTouchScreen(false);
}
static void UiDriver_HandleTouchScreenLongPress(bool is_long_press)
{
    UiDriver_HandleTouchScreen(true);
}

static const keyaction_descr_t keyactions_normal[] =
{
		{ TOUCHSCREEN_ACTIVE, 	UiDriver_HandleTouchScreenShortPress,       UiDriver_HandleTouchScreenLongPress },
		{ BUTTON_F1_PRESSED, 	UiAction_ToggleMenuMode, 					UiAction_SaveConfigurationToMemory },
		{ BUTTON_F2_PRESSED, 	UiAction_ChangeLowerMeterDownOrSnap, 		UiAction_ChangeLowerMeterUp },
		{ BUTTON_F3_PRESSED, 	UiAction_ToggleSplitModeOrToggleMemMode, 	UiAction_ToggleVfoMem },
		{ BUTTON_F4_PRESSED, 	UiAction_ToggleVfoAB, 						UiAction_CopyVfoAB },
		{ BUTTON_F5_PRESSED, 	UiAction_ToggleTuneMode,					UiAction_ToggleTxDisable },
		{ BUTTON_G1_PRESSED, 	UiAction_ChangeDemodMode,					UiAction_ChangeDemodModeToAlternativeMode },
		{ BUTTON_G2_PRESSED, 	UiAction_ChangeToNextDspMode,				UiAction_ToggleDspEnable },
		{ BUTTON_G3_PRESSED, 	UiAction_ChangePowerLevel,					UiAction_ChangePowerLevelToFull },
		{ BUTTON_G4_PRESSED, 	UiAction_ChangeFilterBW,					UiAction_ChangeRxFilterOrFmToneBurst },
		{ BUTTON_M1_PRESSED, 	UiDriver_ChangeEncoderOneMode,				UiAction_ToggleKeyerMode },
		{ BUTTON_M2_PRESSED, 	UiDriver_ChangeEncoderTwoMode,				UiAction_ToggleNoiseblanker },
		{ BUTTON_M3_PRESSED, 	UiDriver_ChangeEncoderThreeMode,			UiAction_ChangeAudioSource },
		{ BUTTON_STEPM_PRESSED, UiAction_ChangeTuningStepDownOrUp,			UiAction_StepMinusHold },
		{ BUTTON_STEPP_PRESSED, UiAction_ChangeTuningStepUpOrDown,			UiAction_StepPlusHold },
		{ BUTTON_BNDM_PRESSED, 	UiAction_ChangeBandDownOrUp,				UiAction_BandMinusHold },
		{ BUTTON_BNDP_PRESSED,  UiAction_ChangeBandUpOrDown,				UiAction_BandPlusHold },
		{ BUTTON_PWR_PRESSED, UiAction_ChangeBacklightBrightness,			UiAction_PowerHold },
#if defined(UI_BRD_OVI40) && (defined(SDR_AMBER) || defined(OVI40_MOD_480_320))
        { BUTTON_E1_PRESSED,    UiDriver_ChangeEncoderOneMode,              UiAction_ToggleKeyerMode },
        { BUTTON_E2_PRESSED,    UiDriver_ChangeEncoderTwoMode,              UiAction_ToggleNoiseblanker },
        { BUTTON_E3_PRESSED,    UiDriver_ChangeEncoderThreeMode,            UiAction_ChangeAudioSource },
//      { BUTTON_E4_PRESSED,    KEYACTION_NOP,                              KEYACTION_NOP },
//      { BUTTON_F6_PRESSED,    KEYACTION_NOP,                              KEYACTION_NOP },
    #ifdef SDR_AMBER_480_320
        { BUTTON_L1_PRESSED,    UiAction_ToggleIllumButt,                   KEYACTION_NOP },
    #endif
#endif
};

static const keyaction_descr_t keyactions_menu[] =
{
		{ BUTTON_F2_PRESSED, 	UiAction_MenuRenderPrevScreen,              UiMenu_RenderFirstScreen },
		{ BUTTON_F3_PRESSED, 	UiAction_MenuRenderNextScreen,              UiMenu_RenderLastScreen },
		{ BUTTON_F4_PRESSED, 	UiAction_MenuSetDefaultValue,               KEYACTION_NOP },
#ifdef HELP_MENU
        { BUTTON_E3_PRESSED,    UiDriver_ShowHelp,                          KEYACTION_NOP },
#endif
};

static const keyaction_descr_t keyactions_keyer[] =
{
		{ BUTTON_F1_PRESSED, 	UiAction_PlayKeyerBtn1,     UiAction_RecordKeyerBtn1 },
		{ BUTTON_F2_PRESSED, 	UiAction_PlayKeyerBtn2,     UiAction_RecordKeyerBtn2 },
		{ BUTTON_F3_PRESSED, 	UiAction_PlayKeyerBtn3,     UiAction_RecordKeyerBtn3 },
//		{ BUTTON_F4_PRESSED, 	KEYACTION_NOP, 		KEYACTION_NOP },
        { BUTTON_F4_PRESSED,    UiAction_PlayKeyerBtn4,     UiAction_RecordKeyerBtn4 },
		{ BUTTON_F5_PRESSED, 	UiAction_ToggleTxRx,        UiAction_ToggleBufferedTXMode },
};

static const keyaction_descr_t keyactions_mems[] =
{
        { BUTTON_F1_PRESSED,    UiDriver_LoadMemData1,                      UiDriver_StoreMemData1 },
        { BUTTON_F2_PRESSED,    UiDriver_LoadMemData2,                      UiDriver_StoreMemData2 },
        { BUTTON_F3_PRESSED,    UiAction_ToggleSplitModeOrToggleMemMode,    UiAction_ToggleVfoMem },
        { BUTTON_F4_PRESSED,    UiDriver_LoadMemData3,                      UiDriver_StoreMemData3 },
        { BUTTON_F5_PRESSED,    UiDriver_LoadMemData4,                      UiDriver_StoreMemData4 },
};

static const keyaction_descr_t keyactions_ext[] =
{
#ifndef SDR_AMBER
        { BUTTON_F1_PRESSED,    UiDriver_RIT_Reset,							KEYACTION_NOP },
#else
        { BUTTON_F1_PRESSED,    UiDriver_RIT_Reset,							UiAction_ToggleIllumButt },
#endif
        { BUTTON_F2_PRESSED,    UiAction_ChangeSpectrumZoomLevelDown,		UiAction_ZoomResetToOne },
        { BUTTON_F3_PRESSED,    UiAction_ChangeSpectrumZoomLevelUp,			UiAction_GoToMainFmenu },
#ifndef SDR_AMBER // not Amber
        { BUTTON_F4_PRESSED,    UiAction_ChangeAudioSource,					KEYACTION_NOP },
        { BUTTON_F5_PRESSED,    Codec_RestartI2S,							KEYACTION_NOP },
#else
        { BUTTON_F4_PRESSED,    UiAction_ChangeRXInputStateDown,			KEYACTION_NOP },
        { BUTTON_F5_PRESSED,    UiAction_ChangeRXInputStateUp,				KEYACTION_NOP },
#endif
};

static const keyaction_descr_t keyactions_xvtr[] =
{
        { BUTTON_F1_PRESSED,    UiDriver_LoadXVTR1,        UiDriver_StoreXVTR1 },
        { BUTTON_F2_PRESSED,    UiDriver_LoadXVTR2,        UiDriver_StoreXVTR2 },
        { BUTTON_F3_PRESSED,    UiAction_ToggleListXVTR,   UiAction_OffXVTR },
        { BUTTON_F4_PRESSED,    UiDriver_LoadXVTR3,        UiDriver_StoreXVTR3 },
        { BUTTON_F5_PRESSED,    UiDriver_LoadXVTR4,        UiDriver_StoreXVTR4 },
};

static const keyaction_descr_t keyactions_choice[] =
{
        { BUTTON_F1_PRESSED,    UiDriver_ChoiceKeyer,        KEYACTION_NOP },
        { BUTTON_F2_PRESSED,    UiDriver_ChoiceExt,          KEYACTION_NOP },
        { BUTTON_F3_PRESSED,    UiAction_ChoiceMems,         KEYACTION_NOP },
        { BUTTON_F4_PRESSED,    UiDriver_ChoiceXvtr,         KEYACTION_NOP },
        { BUTTON_F5_PRESSED,    UiDriver_ChoiceNormal,       KEYACTION_NOP },
};

static const keyaction_list_descr_t key_sets[] =
{
		// ATTENTION: the size calculation only works for true arrays, not for pointers!
		{ keyactions_normal, sizeof(keyactions_normal)/sizeof(*keyactions_normal) },
		{ keyactions_menu, sizeof(keyactions_menu)/sizeof(*keyactions_menu) },
		{ keyactions_keyer, sizeof(keyactions_keyer)/sizeof(*keyactions_keyer) },
        { keyactions_mems, sizeof(keyactions_mems)/sizeof(*keyactions_mems) },
        { keyactions_ext, sizeof(keyactions_ext)/sizeof(*keyactions_ext) },
		{ keyactions_xvtr, sizeof(keyactions_xvtr)/sizeof(*keyactions_xvtr) },
		{ keyactions_choice, sizeof(keyactions_choice)/sizeof(*keyactions_choice) },
};

static void UiDriver_HandleKeyboard(void)
{
	if(ks.button_processed)
	{
		UiDriver_LcdBlankingStartTimer();	// calculate/process LCD blanking timing
		AudioManagement_KeyBeep();  // make keyboard beep, if enabled

		bool keyIsProcessed = false;

        if (ts.MenuOfFMenusActive == true)
        {
            keyIsProcessed = UiDriver_ProcessKeyActions(&key_sets[6]);
        }
        if (keyIsProcessed == false && ts.XvtrFMenuActive == true)
        {
            keyIsProcessed = UiDriver_ProcessKeyActions(&key_sets[5]);
        }
		if (keyIsProcessed == false && ts.ExtFMenuActive == true)
		{
			keyIsProcessed = UiDriver_ProcessKeyActions(&key_sets[4]);
		}
//		if (ts.vfo_mem_flag == true)
		if (keyIsProcessed == false && ts.vfo_mem_flag == true)
        {
            keyIsProcessed = UiDriver_ProcessKeyActions(&key_sets[3]);
        }
//		if (ts.keyer_mode.active == true)
		if (keyIsProcessed == false && ts.keyer_mode.active == true)
		{
			keyIsProcessed = UiDriver_ProcessKeyActions(&key_sets[2]);
		}
		if (keyIsProcessed == false && ts.menu_mode == true)
		{
			keyIsProcessed = UiDriver_ProcessKeyActions(&key_sets[1]);
		}
		if (keyIsProcessed == false)
		{
			keyIsProcessed = UiDriver_ProcessKeyActions(&key_sets[0]);
		}

		// Reset flag, allow other buttons to be checked
		ks.button_processed = 0;
		ks.debounce_time	= 0;
	}
}


typedef enum {
	SCTimer_ENCODER_KEYS =0, // 10ms
	SCTimer_RTC, // 100 * 10ms
	SCTimer_LODRIFT, // 64 * 10ms
	SCTimer_VOLTAGE, // 8 * 10ms
	SCTimer_SMETER, // 4 * 10ms
	SCTimer_MAIN, // 4 * 10ms
	SCTimer_LEDBLINK, // 64 * 10ms
    SCTimer_SAM, // 25 * 10ms
	SCTimer_NUM
} SysClockTimers;

uint32_t last_sysclock_seen[SCTimer_NUM];


/*
 * Implements a simple timeout timer.
 * Returns true if the current sysclock differs by equal or more than divider cycles.
 * Dividers should be powers of 2 to generate optimal code
 */
bool UiDriver_TimerIsExpired(SysClockTimers sct,uint32_t now, uint32_t divider)
{
	return (last_sysclock_seen[sct] != now/divider);
}

/**
 * @brief Implements a simple timeout timer. Sets the time to now/divider, so it will expire in now+divider cycles
 * Dividers should be powers of 2 to generate optimal code
 */
void UiDriver_TimerRewind(SysClockTimers sct,uint32_t now, uint32_t divider)
{
	last_sysclock_seen[sct] = now/divider;
}

/**
 * @brief Implements a simple timeout timer. If expired the timer is automatically restarted.
 *
 * @param sct the timer data structure
 * @param divider should be powers of 2 to generate optimal code
 * @param now current sysclock value
 *
 * @returns true if the now differs by equal or more than divider cycles.
 *
 */
bool UiDriver_TimerExpireAndRewind(SysClockTimers sct,uint32_t now, uint32_t divider)
{
	bool retval = UiDriver_TimerIsExpired(sct, now, divider);
	if (retval)
	{
		UiDriver_TimerRewind(sct, now, divider);
	}
	return retval;
}



#ifdef USE_USBHOST
#include "usb_host.h"
#include "usbh_core.h"
#include "usbh_hid_keybd.h"
extern USBH_HandleTypeDef hUsbHostHS;
#endif

/**
 * This handler is activated by the Audio-Interrupt with a frequency of 1500 Hz via PendSV interrupt.
 * However, since this handler may run longer than 0.66uS it cannot be used to keep track of time
 * or you cannot count on it called every 0.66uS. It will be next activate no longer than 0.66uS after
 * last finishing the handler.
 *
 * This handler is executed as PendSV interrupt handler with lowest possible priority, i.e. its execution
 * is interrupted by all other interrupts with a higher priority.
 *
 * Please note: Do not call any UI related functions here, essentially we should only include high prio
 * tasks such as longer running audio processing (e.g. FreeDV or the NR processing) which should be executed
 * with as low latency as possible but take too long for being included in the audio interrupt handler itself
 * Also relevant: If this handler function does take too long, the UI handling including display updates
 * will feel sluggish or will not work at all since all processing time is spend here then and
 * the main tasks will never get executed.
 *
 * In a nutshell, unless there is a very, very good reason, do not add or change anything here.
 *
 */
void UiDriver_TaskHandler_HighPrioTasks()
{
    // READ THE LENGTHY COMMENT ABOVE BEFORE CHANGING ANYTHING BELOW!!!
    // YES, READ IT! Thank you!
    if (ads.af_disabled == false)
    {
        // we only process these audio things if rx data processing is active
#ifdef USE_FREEDV
        if (ts.dmod_mode == DEMOD_DIGI && ts.digital_mode == DigitalMode_FreeDV)
        {
            FreeDv_HandleFreeDv();
        }
#endif // USE_FREEDV

#ifdef USE_ALTERNATE_NR
        if ((is_dsp_nb_active() || is_dsp_nr()) && (ads.decimated_freq == 12000))
        {
          AudioNr_HandleNoiseReduction();
        }
#endif
    }
#ifdef USE_HIGH_PRIO_PTT
    if (RadioManagement_SwitchTxRx_Possible())
    {
        RadioManagement_HandlePttOnOff();
    }
#endif
#ifdef USE_CONVOLUTION
    	convolution_handle();
#endif

}

/**
 * Handles the regular updating and running of some real-time activities in the UI domain
 * mostly timers, do not place long running code in here. This is running the highest prio
 * interrupt context we have. So don't place anything in here unless you know what you are doing
 * and there is no other way to do it.
 *
 * Called with IQ_INTERRUPT_FREQ (i.e. 1500 Hz at the moment).
 */
void UiDriver_Callback_AudioISR()
{
    static uint32_t tcount = 0;

    // Perform LCD backlight PWM brightness function
    UiDriver_BacklightDimHandler();

    tcount+= SAMPLES_PER_DMA_CYCLE;        // add the number of samples that have passed in DMA cycle
    if(tcount >= SAMPLES_PER_CENTISECOND)  // enough samples for 0.01 second passed?
    {
        tcount -= SAMPLES_PER_CENTISECOND;  // yes - subtract that many samples
        ts.sysclock++;  // this clock updates at PRECISELY 100 Hz over the long term
    }

#ifdef FAST_FREQ_ENC
    if(ts.freq_enc_timer > 0)
    {
        ts.freq_enc_timer--;
    }
    else
    {
        ts.freq_enc_timer = 3; // 2 ms
        UiDriver_CheckFrequencyEncoder();
    }
#endif

    // Has the timing for the keyboard beep expired?
    if(ts.beep_timing > 0)
    {
        ts.beep_timing--;
    }

    if(ts.scope_scheduler)      // update thread timer if non-zero
    {
        ts.scope_scheduler--;
    }

    if(ts.waterfall.scheduler)      // update thread timer if non-zero
    {
        ts.waterfall.scheduler--;
    }

    if(ts.audio_spkr_unmute_delay_count)        // this updates at 1.5 kHz - used to time TX->RX delay
    {
        ts.audio_spkr_unmute_delay_count--;
    }

    if(ks.debounce_time < DEBOUNCE_TIME_MAX)
    {
        ks.debounce_time++;   // keyboard debounce timer
    }
}

static void UiDriver_HandleUSB_Keyboard(void)
{
#ifdef USE_USBKEYBOARD
    if(USBH_HID_GetDeviceType(&hUsbHostHS) == HID_KEYBOARD)
    {
        HID_KEYBD_Info_TypeDef *k_pinfo = USBH_HID_GetKeybdInfo(&hUsbHostHS);

        const uint32_t kb_buffer_size = (sizeof(k_pinfo->keys)/sizeof(k_pinfo->keys[0]));
        static uint8_t keys_buffer[sizeof(k_pinfo->keys)/sizeof(k_pinfo->keys[0])] = { 0 };

        /*
         * Regarding -> https://www.usb.org/sites/default/files/documents/hid1_11.pdf 72p.
         *
         * The order of keycodes in array fields has no significance. Order determination
         * is done by the host software comparing the contents of the previous report to
         * the current report. If two or more keys are reported in one report, their order is
         * indeterminate. Keyboards may buffer events that would have otherwise
         * resulted in multiple event in a single report.
         *
         * So, the code below is keeping the previous report from the keyboard to compare
         * with the next one and filter out multi-pressed keys. Later this could be used
         * to determine long press key or repeat key if it's holding - for example:
         *
         * Start recording F1-button if F5 was pressed for 2 sec...
         */
        if(k_pinfo != NULL)
        {
            /*
             * The discussion about code below was there
             * -> https://github.com/df8oe/UHSDR/pull/1702
             */
            for( uint32_t idx = 0; idx < kb_buffer_size; idx++ )
            {
                bool is_exist_in_buffer = false;
                for( uint32_t i = 0; i < kb_buffer_size; i++ )
                {
                    /*
                     * Looking for the same symbol in prev. array by iterating over this array.
                     */
                    if ( keys_buffer[i] == k_pinfo->keys[idx])
                    {
                        /*
                         * if symbol presents in both arrays that means
                         * it's already handled and we need to ignore it.
                         */
                        is_exist_in_buffer = true;
                        break;
                    }
                }
                if ( is_exist_in_buffer == true )
                {
                    /*
                     * The same character was found in previous report,
                     * so, Ignoring this one
                     */
                    continue;
                }
                else
                {
                    switch(k_pinfo->keys[idx])
                    {
                    case KEY_ESCAPE:
                      DigiModes_TxBufferReset();
                      break;
                    case KEY_BACKSPACE:
                        UiDriver_TextMsgClear();
                      break;
                    case KEY_F1:
                      RadioManagement_Request_TxOn();
                      break;
                    case KEY_F2:
                      RadioManagement_Request_TxOff();
                      break;
                    case KEY_F5:
                        if (k_pinfo->lshift)
                        {
                            UiAction_RecordKeyerBtn1();
                        }
                        else
                        {
                            UiAction_PlayKeyerBtn1();
                        }
                        break;
                    case KEY_F6:
                        if (k_pinfo->lshift)
                        {
                            UiAction_RecordKeyerBtn2();
                        }
                        else
                        {
                            UiAction_PlayKeyerBtn2();
                        }
                        break;
                    case KEY_F7:
                        if (k_pinfo->lshift)
                        {
                            UiAction_RecordKeyerBtn3();
                        }
                        else
                        {
                            UiAction_PlayKeyerBtn3();
                        }
                        break;
                    case KEY_F8:
                        if (k_pinfo->lshift)
                        {
                            UiAction_ToggleBufferedTXMode();
                        }
                        else
                        {
                            UiAction_ToggleKeyerMode();
                        }
                      break;

                    }

                    uint8_t kbdChar = USBH_HID_GetASCIICode( k_pinfo, idx );
                    if (kbdChar != '\0')
                    {
                      // FIXME seems we can push only into Digi_buffer....
                      if (is_demod_rtty() || is_demod_psk() || ts.dmod_mode == DEMOD_CW)
                      {
                          DigiModes_TxBufferPutChar( kbdChar, KeyBoard );
                      }
                      else
                      {
                          UiDriver_TextMsgPutChar( kbdChar );
                      }
                    }
                }
            }
            memcpy( keys_buffer, k_pinfo->keys, sizeof(k_pinfo->keys));
        }
    }
#endif // USE_USBKEYBOARD
}

void UiDriver_TaskHandler_MainTasks()
{

	uint32_t now = ts.sysclock;
	//        HAL_GetTick()/10;
	RadioManagement_TxRxSwitching_Disable();
	CatDriver_HandleProtocol();
	RadioManagement_TxRxSwitching_Enable();

#ifndef USE_PENDSV_FOR_HIGHPRIO_TASKS
	UiDriver_TaskHandler_HighPrioTasks();
#endif

    // START CALLED AS OFTEN AS POSSIBLE
#ifndef USE_HIGH_PRIO_PTT
	if (ts.tx_stop_req == true  || ts.ptt_req == true)
	{
		RadioManagement_HandlePttOnOff();
	}
#endif
	// END CALLED AS OFTEN AS POSSIBLE

#ifdef USE_USBHOST
    MX_USB_HOST_Process();
#endif // USE_USBHOST

	// BELOW ALL CALLING IS BASED ON SYSCLOCK 10ms clock
	if (UiDriver_TimerExpireAndRewind(SCTimer_ENCODER_KEYS,now,1))
	{
		// 10ms have elapsed.
		// Now process events which should be handled regularly at a rate of 100 Hz
		// Remember to keep this as short as possible since this is executed in addition
		// to all other processing below.
	    RadioManagement_TxRxSwitching_Disable();
		UiDriver_CheckEncoderOne();
		UiDriver_CheckEncoderTwo();
		UiDriver_CheckEncoderThree();
#ifndef FAST_FREQ_ENC
		UiDriver_CheckFrequencyEncoder();
#endif
        UiDriver_CheckCycledKeyerTX();
		UiDriver_KeyboardProcessOldClicks();
#ifndef USE_HIGH_PRIO_PTT
		RadioManagement_HandlePttOnOff();
#endif
		RadioManagement_UpdatePowerAndVSWR();
		RadioManagement_TxRxSwitching_Enable();
		if (ts.twinpeaks_tested == TWINPEAKS_CODEC_RESTART)
		{
		    Codec_RestartI2S();
		    ts.twinpeaks_tested = TWINPEAKS_WAIT;
		}
        UiDriver_HandleUSB_Keyboard();
	}

	UiSpectrum_Redraw();

	// Expect the code below to be executed around every 40 - 80ms.
	// The exact time between two calls is unknown and varies with different
	// display options  (waterfall/scope, DSP settings etc.)
	// Nothing with short intervals < 100ms  and/or need for very regular intervals between calls
	// should be placed in here.
	if(UiDriver_TimerIsExpired(SCTimer_MAIN,now,1))            // bail out if it is not time to do this task
	{
		switch(drv_state)
		{
		case STATE_S_METER:
		    // we update all the meters (either TX or RX) no more than 25 times a second
			if (UiDriver_TimerExpireAndRewind(SCTimer_SMETER,now,4))
			{
	            UiDriver_HandleTXMeters();
	            RadioManagement_HandleIqGainAndSMeter();
				UiDriver_HandleSMeter();
#ifdef USE_FREEDV
				if (ts.dmod_mode == DEMOD_DIGI && ts.digital_mode == DigitalMode_FreeDV)
				{
			        FreeDv_DisplayUpdate();
				}
#endif // USE_FREEDV

			}
			break;
		case STATE_HANDLE_POWERSUPPLY:
			Board_HandlePowerDown();

			if (UiDriver_TimerExpireAndRewind(SCTimer_VOLTAGE,now,8))
			{
				if (UiDriver_HandleVoltage())
				{
					UiDriver_DisplayVoltage();
				}

				if (pwmt.undervoltage_detected == true) {
					if (UiDriver_TimerExpireAndRewind(SCTimer_LEDBLINK, now, 64)) {
						Board_GreenLed(LED_STATE_TOGGLE);
					}
				}
				UiDriver_TextMsgDisplay();
			}
			break;
		case STATE_LO_TEMPERATURE:
			if (UiDriver_TimerExpireAndRewind(SCTimer_LODRIFT,now,64))
			{
				UiDriver_HandleLoTemperature();
#if 1
				ProfilingTimedEvent* pe_ptr = profileTimedEventGet(ProfileAudioInterrupt);

				// Percent audio interrupt load  = Num of cycles per audio interrupt  / ((max num of cycles between two interrupts ) / 100 )
				//
				// Num of cycles per audio interrupt = cycles for all counted interrupts / number of interrupts
				// Max num of cycles between two interrupts / 100 = HCLK frequency / Interruptfrequenz -> e.g. 168000000 / 1500 / 100 = 1120
				// FIXME: Need to figure out which clock is being used, 168000000 in mcHF, I40 UI = 168.000.000 or 216.000.000 or something else...

				uint32_t load =  pe_ptr->duration / (pe_ptr->count * (1120));
				profileTimedEventReset(ProfileAudioInterrupt);
				char str[20];
				snprintf(str,20,"L%3u%%",(unsigned int)load);
				if(ts.show_debug_info)
				{
					UiLcdHy28_PrintText(ts.Layout->LOAD_X,ts.Layout->LOADANDDEBUG_Y,str,White,Black,0);
				}
#endif
			}
			if (UiDriver_TimerExpireAndRewind(SCTimer_RTC,now,100))
			{
				if (ts.rtc_present)
				{
					RTC_TimeTypeDef sTime;


					Rtc_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);

					char str[20];
					snprintf(str,20,"%2u:%02u:%02u",sTime.Hours,sTime.Minutes,sTime.Seconds);
					UiLcdHy28_PrintText(ts.Layout->RTC_IND.x, ts.Layout->RTC_IND.y, str, White, Black, 0);
				}
			}
			break;
		case STATE_TASK_CHECK:
		    RadioManagement_TxRxSwitching_Disable();
			UiDriver_TimeScheduler();
			RadioManagement_TxRxSwitching_Enable();
			// Handles live update of Calibrate between TX/RX and volume control
			break;
		case STATE_UPDATE_FREQUENCY:
			/* at this point we handle request for changing the frequency
			 * either from a difference in dial freq or a temp change
			 *  */
			if((df.tune_old != df.tune_new))
			{
			    RadioManagement_TxRxSwitching_Disable();
				UiDriver_FrequencyUpdateLOandDisplay(false);
				RadioManagement_TxRxSwitching_Enable();
				UiDriver_DisplayMemoryLabel();				// this is because a frequency dialing via CAT must be indicated if "CAT in sandbox" is active
				UiSpectrum_DisplayFilterBW();
			}
			else if (df.temp_factor_changed  || ts.tune_freq != ts.tune_freq_req)
			{
				// this handles the cases where the dial frequency remains the same but the
				// LO tune frequency needs adjustment, e.g. in CW mode  or if temp of LO changes
			    RadioManagement_TxRxSwitching_Disable();
				RadioManagement_ChangeFrequency(false,df.tune_new, ts.txrx_mode);
				RadioManagement_TxRxSwitching_Enable();
			}
            else
            {
                // this handles cases in which switch from tx to rx and vice versa
                // changes the displayed frequency. This is currently only the case
                // if in xverter mode and we have tx_offset != rx_offset and is not
                // split mode.
                static uint8_t last_seen_txrx_mode = TRX_MODE_RX;
                if (ts.txrx_mode != last_seen_txrx_mode)
                {
                    last_seen_txrx_mode = ts.txrx_mode;
                    if (RadioManagement_Transverter_IsEnabled()
                            && is_splitmode() == false
                            && ts.xverter_offset_tx != 0 // tx offset enabled
                            && ts.xverter_offset != ts.xverter_offset_tx) // and not equal rx
                    {
                        RadioManagement_TxRxSwitching_Disable();
                        UiDriver_FrequencyUpdateLOandDisplay(false);
                        RadioManagement_TxRxSwitching_Enable();
                    }
                }
            }
			break;
		case STATE_PROCESS_KEYBOARD:
			UiDriver_HandleKeyboard();
			break;
		case STATE_DISPLAY_SAM_CARRIER:
			if (UiDriver_TimerExpireAndRewind(SCTimer_SAM,now,25))
			{
				if(ts.dmod_mode == DEMOD_SAM)
				{
					UiDriver_UpdateLcdFreq(df.tune_old, Yellow, UFM_SECONDARY);
				}
				else if (ts.dmod_mode == DEMOD_CW && cw_decoder_config.snap_enable)
				{
					//UiDriver_UpdateLcdFreq(ads.snap_carrier_freq, Green, UFM_SECONDARY);
				}
		// display AGC box and AGC state
				// we have 5 states -> We can collapse 1 and 2 -> you see this in the box title anyway
				// we use an asterisk to indicate action
				// 1 OFF -> WDSP AGC not active
				// 2 ON + NO_HANG + NO ACTION		no asterisk
				// 3 ON + HANG_ACTION + NO ACTION 	white asterisk
				// 4 ON + ACTION                	green asterisk
				// 5 ON + ACTION + HANG_ACTION  	blue asterisk
				const char* txt = "   ";
				uint16_t AGC_bg_clr = Black;
				uint16_t AGC_fg_clr = Black;

				if(agc_wdsp_conf.hang_action == 1 && agc_wdsp_conf.hang_enable == 1)
				{
					AGC_bg_clr = White;
					AGC_fg_clr = Black;
				}
				else
				{
//					AGC_bg_clr = Blue;
				    AGC_bg_clr = sd.boxes_colour;
					AGC_fg_clr = White;
				}
				if(agc_wdsp_conf.action == 1)
				{
					txt = "AGC";
				}

//				UiLcdHy28_PrintTextCentered(ts.Layout->DEMOD_MODE_MASK.x - 41,ts.Layout->DEMOD_MODE_MASK.y,ts.Layout->DEMOD_MODE_MASK.w-6,txt,AGC_fg_clr,AGC_bg_clr,0);
#ifndef SDR_AMBER
				UiLcdHy28_PrintTextCentered(ts.Layout->AGC_MASK.x,ts.Layout->AGC_MASK.y,ts.Layout->AGC_MASK.w,txt,AGC_fg_clr,AGC_bg_clr,0);
#else
	#ifndef SDR_AMBER_480_320
				if(!ts.show_wide_spectrum)
				{
					UiLcdHy28_PrintTextCentered(ts.Layout->AGC_MASK.x,ts.Layout->AGC_MASK.y,ts.Layout->AGC_MASK.w,txt,AGC_fg_clr,AGC_bg_clr,0);
				}
				else
				{
	#endif
					char* input_label = "   ";
					if(ts.amber_input_state == 0)
					{
						input_label = "PRE";
					}
					else if(ts.amber_input_state == 1)
					{
						input_label = "NoPRE";
					}
					else if(ts.amber_input_state == 2)
					{
						input_label = "ATT12";
					}
					else if(ts.amber_input_state == 3)
					{
						input_label = "ATT24";
					}
					UiLcdHy28_PrintTextCentered(ts.Layout->AGC_MASK.x,ts.Layout->AGC_MASK.y,46,input_label,White,AGC_bg_clr,0);
					UiLcdHy28_PrintTextCentered(ts.Layout->AGC_MASK.x + 46,ts.Layout->AGC_MASK.y,34,txt,AGC_fg_clr,AGC_bg_clr,0);
	#ifndef SDR_AMBER_480_320
				}
	#endif
#endif
				// display CW decoder WPM speed
				if(ts.cw_decoder_enable && ts.dmod_mode == DEMOD_CW)
				{
					CwDecoder_WpmDisplayUpdate(false);
				}

			}
			break;
		default:
			break;

		}
		if (drv_state < STATE_MAX)
		{
			// advance to next state
			drv_state++;
		}
		else
		{
			UiDriver_TimerRewind(SCTimer_MAIN,now,1);
			// wrap state to first state
			drv_state = 0;
		}
	}
}

/*
 * This handler creates a software pwm for the LCD backlight. It needs to be called
 * very regular to work properly. Right now it is activated from the audio interrupt
 * at a rate of 1.5khz The rate itself is not too critical,
 * just needs to be high and very regular.
 */

#define LCD_DIMMING_PWM_COUNTS 16

void UiDriver_BacklightDimHandler()
{
	static uchar lcd_dim = 0;
	static const uint16_t dimming_pattern_map[1 + LCD_DIMMING_LEVEL_MAX - LCD_DIMMING_LEVEL_MIN] =
	{
	        0xffff, // 16/16
	        0x3f3f, // 12/16
	        0x0f0f, // 8/16
	        0x0303, // 4/16
	        0x0101, // 2/16
	        0x0001, // 1/1
	};
    // most of the patterns generate a 1500/8 =  187.5 Hz noise, lowest 1500/16 = 93.75 Hz.

	static uint16_t dim_pattern = 0xffff; // gives us the maximum brightness

	if(!ts.lcd_blanking_flag)       // is LCD *NOT* blanked?
	{
	    if (lcd_dim == 0 )
	    {
	        dim_pattern = dimming_pattern_map[ts.lcd_backlight_brightness - LCD_DIMMING_LEVEL_MIN];
	    }

	    // UiLcdHy28_BacklightEnable(lcd_dim >= dimming_map[ts.lcd_backlight_brightness - LCD_DIMMING_LEVEL_MIN]);   // LCD backlight off or on
	    UiLcdHy28_BacklightEnable((dim_pattern & 0x001) == 1);   // LCD backlight off or on

	    dim_pattern >>=1;
	    lcd_dim++;
	    lcd_dim %= LCD_DIMMING_PWM_COUNTS;   // limit brightness PWM count to 0-3
	}
	else if(!ts.menu_mode)
	{ // LCD is to be blanked - if NOT in menu mode
		UiLcdHy28_BacklightEnable(false);
	}
}

void UiDriver_ToggleWideSpectrum()
{
	if(ts.show_wide_spectrum)
	{
		ts.Layout = &LcdLayouts[LcdLayout_320x240_ws];
	}
	else
	{
    	ts.Layout=&LcdLayouts[LcdLayout_320x240];
	}
	incr_wrap_uint8(&sd.magnify,MAGNIFY_MIN,MAGNIFY_MAX);
	UiDriver_SpectrumChangeLayoutParameters();
	decr_wrap_uint8(&sd.magnify,MAGNIFY_MIN,MAGNIFY_MAX);
	UiDriver_SpectrumChangeLayoutParameters();

    UiDriver_CreateDesktop();
    UiDriver_DisplayBandForFreq(df.tune_new, true);
	UiDriver_DisplayVoltage();
	UiDriver_HandleLoTemperature();
	UiDriver_UpdateDemodSpecificDisplayAfterParamChange();
}

//void UiDriver_ToggleWideSpectrumOnStart()
//{
//	if((ts.expflags1 & EXPFLAGS1_WIDE_SPEC_DEF) && (ts.Layout->Size.x == 320))
//	{
//	ts.show_wide_spectrum = 1;
////	HAL_Delay(2000);
//	UiDriver_ToggleWideSpectrum();
//	}
//}

static void UiDriver_LoadXvtrData(uint8_t xvtr_cell)
{
    if(xvtr_data[xvtr_cell][0] & 0xf)
    {
    //Don't show BndMem
    ts.expflags1 = ts.expflags1 | EXPFLAGS1_NO_SHOW_BNDMEM;
    UiDriver_ClearMemoryLabel();

    ts.xverter_mode = xvtr_data[xvtr_cell][0];
    ts.xverter_offset = xvtr_data[xvtr_cell][1];
    ts.xverter_offset_tx = xvtr_data[xvtr_cell][2];

    UiDriver_CreateMainFreqDisplay(true);
    UiDriver_FrequencyUpdateLOandDisplay(true);

#ifdef SDR_AMBER
    if (ts.expflags2 & EXPFLAGS2_XVTR_OFF_PA)
    {
        ts.amber_io8_state = ts.amber_io8_state & ~128; // reset bit
        Board_AmberIOx8_Write(ts.amber_io8_state);
    }
#endif
    }
}

static void UiDriver_LoadXVTR1()
{
    UiDriver_LoadXvtrData(0);
}

static void UiDriver_LoadXVTR2()
{
    UiDriver_LoadXvtrData(1);
}

static void UiDriver_LoadXVTR3()
{
    UiDriver_LoadXvtrData(2);
}

static void UiDriver_LoadXVTR4()
{
    UiDriver_LoadXvtrData(3);
}

static void UiDriver_StoreXvtrData(uint8_t xvtr_cell)
{
    xvtr_data[xvtr_cell][0] = ts.xverter_mode;
    xvtr_data[xvtr_cell][1] = ts.xverter_offset;
    xvtr_data[xvtr_cell][2] = ts.xverter_offset_tx;
}

static void UiDriver_StoreXVTR1()
{
    UiDriver_StoreXvtrData(0);
}

static void UiDriver_StoreXVTR2()
{
    UiDriver_StoreXvtrData(1);
}

static void UiDriver_StoreXVTR3()
{
    UiDriver_StoreXvtrData(2);
}

static void UiDriver_StoreXVTR4()
{
    UiDriver_StoreXvtrData(3);
}

static void UiAction_ShowListXVTR(void)
{
        char txt_cell[5];
        char txt_mult[5];
        char txt_rx[12];
        char txt_tx[15];
        uint32_t temp;
        uint32_t offset_offset = XVERTER_OFFSET_MAX_HZ-(XVERTER_OFFSET_MAX_HZ/1000);
        uint8_t y_start = !ts.show_wide_spectrum?135:140;
        uint8_t y_interval = 21;

        for(int cell = 0; cell<4; cell++)
        {
            snprintf(txt_cell, 32, "XVTR%1u", cell+1);
            UiLcdHy28_PrintText(10+(!ts.show_wide_spectrum?70:0),y_start+(cell*y_interval), txt_cell,Yellow,Black,0);
            temp = xvtr_data[cell][0];
            if(temp & 0xf && ((uint)temp < 400)) // cell is no empty
            {
                snprintf(txt_mult,32, "x%u", (uint)temp);
                UiLcdHy28_PrintText(60+(!ts.show_wide_spectrum?70:0),y_start+(cell*y_interval), txt_mult,Yellow,Black,0);
                temp = xvtr_data[cell][1];
                if(temp > XVERTER_OFFSET_MAX_HZ)
                {
                    sprintf(txt_rx,"RX-k%9ld", temp-offset_offset);
                }
                else
                {
                    sprintf(txt_rx,"RX- %9ld", temp);
                }
                UiLcdHy28_PrintText(94+(!ts.show_wide_spectrum?70:0),y_start+(cell*y_interval), txt_rx,Yellow,Black,0);
                temp = xvtr_data[cell][2];
                if(temp > XVERTER_OFFSET_MAX_HZ)
                {
                    sprintf(txt_tx,"TX-k%9ld", temp-offset_offset);
                }
                else if(temp == 0)
                {
                    sprintf(txt_tx,"TX-%s", " as RX");
                }
                else
                {
                    sprintf(txt_tx,"TX- %9ld", temp);
                }
                UiLcdHy28_PrintText(94+(!ts.show_wide_spectrum?70:0),y_start+10+(cell*y_interval), txt_tx,Blue2,Black,0);
            }
            else
            {
                UiLcdHy28_PrintText(60+(!ts.show_wide_spectrum?70:0),y_start+(cell*y_interval), "empty",White,Black,0);
            }
        }
}

static void UiAction_ToggleListXVTR()
{
    UiSpectrum_Clear();
    ts.xvtr_disp = !ts.xvtr_disp;
    if (!ts.xvtr_disp)
    {
        UiSpectrum_Init();  // init spectrum scope
    }
    else
    {
        UiAction_ShowListXVTR();
    }
}

static void UiAction_OffXVTR()
{
    ts.xverter_mode = 0;
    ts.XvtrFMenuActive = false;
    ts.xvtr_disp = false;
    UiDriver_CreateMainFreqDisplay(true);
    UiDriver_FrequencyUpdateLOandDisplay(true);
    UiSpectrum_Init();
    UiDriver_CreateFunctionButtons(false);

#ifdef SDR_AMBER
    if (ts.expflags2 & EXPFLAGS2_XVTR_OFF_PA)
    {
        ts.amber_io8_state = ts.amber_io8_state | 128; // set bit
        Board_AmberIOx8_Write(ts.amber_io8_state);
    }
#endif
}

static void UiDriver_ChoiceKeyer()
{
    ts.MenuOfFMenusActive = false;
    ts.keyer_mode.active = true;
    UiDriver_CreateFunctionButtons(false);
}

static void UiDriver_ChoiceExt()
{
    ts.MenuOfFMenusActive = false;
    ts.ExtFMenuActive = true;
    UiDriver_CreateFunctionButtons(false);
}

static void UiAction_ChoiceMems()
{
    ts.MenuOfFMenusActive = false;
    ts.vfo_mem_flag = true;
    UiDriver_CreateFunctionButtons(false);
}

static void UiDriver_ChoiceXvtr()
{
    ts.MenuOfFMenusActive = false;
    ts.XvtrFMenuActive = true;
    UiDriver_CreateFunctionButtons(false);
}

static void UiDriver_ChoiceNormal()
{
    ts.MenuOfFMenusActive = false;
    UiDriver_CreateFunctionButtons(false);
}

#ifdef HELP_MENU
static void UiDriver_ShowHelp(void)
{
//    if(!ts.menu_mode || !ts.show_wide_spectrum)
//    {
//        // Do nothing
//        return;
//    }

    if(ts.help_showing)
    {
        ts.help_showing = false;
        UiSpectrum_Clear();
        UiMenu_RenderMenu(MENU_RENDER_ONLY);
    }
    else
    {
        ts.help_showing = true;
        UiSpectrum_Clear();

        // test
//        char buf[317] = "Threshold = Knee of the AGC: input signal level from which on the AGC action takes place. AGC threshold should be placed/adjusted just above the band noise for every particular RX situation to allow for optimal AGC action. The blue AGC box indicates when AGC action takes place and helps in adjusting this threshold.\0";
        //
        char buf[500] = "No comment\0";
        //char *buf = "No comment\0";
        uint16_t size_of_helps = sizeof(Helps)/sizeof(Helps[0]);
        uint16_t index = ts.menu_selected_item;
        uint16_t a = 0;
        uint16_t s = 0;
        for(a = 0; a <= size_of_helps; a++)
        {
            if(Helps[a].count == index)
            {
                for(s = 0; Helps[a].txt[s] != '\0'; s++)
                {
                buf[s] = Helps[a].txt[s];
                }
                buf[s+1] = '\0';
                break;
            }
        }


        uint16_t i = 0;

        uint16_t max_simb_in_line = 56; // for 480x320 screen
        uint16_t max_line_counter = 13;
        uint16_t start_y = 130;
        uint16_t start_x = 14;
        uint16_t between_lines = 14;

        if(ts.Layout->Size.x == 320) // for 320x240 screen
        {
            max_simb_in_line = 32;
            max_line_counter = 7;
            start_y = 130;
            start_x = 10;
            between_lines = 12;
        }

        uint16_t line_counter = 0;
        uint16_t ii = 0;
        char line[max_simb_in_line + 1];

         do {
                line[ii] = buf[i];
                ii++;
                if((ii >= max_simb_in_line)  && (line_counter <= max_line_counter))
                {
                    line[ii +1] = '\0';
                    UiLcdHy28_PrintText(start_x, start_y + (line_counter * between_lines), line, White, Black, 0);
                    line_counter++;
                    ii = 0;
                }
                i++;
         }    while (buf[i] != '\0');

         if(line_counter <= max_line_counter)
         {
             line[ii +1] = '\0';
             UiLcdHy28_PrintText(start_x, start_y + (line_counter * between_lines), line, White, Black, 0);
         }
    }
}
#endif
