Central Idaho Amateur
Radio Club
     
Automated SG-230 LOCK for Icom IC-F8101-3X

This page is currently under construction. This notice will be removed when this article has been completed. The project that is the subject of this article is also under construction, and the source code has not yet been verified or debugged. When this notice is removed, all content can be considered to be finalized.


Configuration of the IC-F8101-3X for ALE Operation


Table of Contents
Overview

SGC markets an SGC LOCK device, which can be used to enabling tuning of the SGC tuner, or to lock the tuner to the current tune setting once the tuning process has been completed. All of this requires manual intervention by the operator for what often is mobile operation.

The SGC LOCK Device can be cumbersome to use, requiring manual unlocking of the device when moving to a new frequency, changing the radio mode to produce a carrier, keying down to initiate tuning, locking the tuner to prevent tuning from occurring while in communications, and then restoring the radio operating mode. By automating these tasks, and configuring the IC-F8101-3X to indicate that an auto-tuner is attached, the operator need only change frequencies and key the microphone (provided PTT-TUNE is enabled in the radio). For ALE operations, the radio will initiate the auto-tune sequence without operator intervention.

This project uses an Arduino Pro Mini, and a small number of MOSFET transistors, to automate the LOCK device, by emulating an Icom Auto-Tuner. Features will include:

If tuning is not successful, the tuner is left un-locked.

The result is that, when the IC-F8101-3X is configured to have a TUNER attached, ordered synchronous auto-tuning will occur with the SGC SG-230 auto-tuner, including ALE operations. Additionally, the tuner will be self-locked after tuning has completed and will remain locked until the transceiver initiates a new auto-tune sequence, preventing asynchronous auto-tune sequences while communications are being carried out.

Tuner DC power is routed from the Icom IC-F8101-3X ATU connector, to the SGC SG-230 Auto Tuner, making a single point of connection for both the SGC SG-230 and this control module.


Icom Tune Sequence

The following trace shows the Icom IC-F8101-3X initiating an auto-tune sequence, using an Elecraft KAT500 Antenna Tuner, which has an Icom AH-4 compatible auto-tuner interface. Trace 1 depicts the KEY signal that is asserted by the auto-tuner while trace 2 shows the 500 millisecond TUNE signal that initiates the auto-tune sequence. The IC-F8101-3X will assert the TUNE signal for 500 milliseconds. At 240 milliseconds after the negation of the TUNE signal, the tuner responds by asserting the KEY line signal, which keys the radio and performs the auto-tune sequence.


Icom tune sequence

Icom tune sequence

State Machine

The following state diagram shows state positions and the events that trigger a transition from one state position to the next.


state diagram

Schematic

schematic

Source Code
/*
    Copyright © 2017
    Raymond B Montagne
    All rights reserved

    SGC SG-230 ICOM AUTO-LOCK TUNER CONTROLLER
*/

//	PIN ASSIGNMENTS

const unsigned int	kPIN__TUNE_START					= 2;
const unsigned int	kPIN__LOCK							= 3;
const unsigned int	kPIN__KEY							= 4;
const unsigned int	kPIN__TUNED_STATUS					= 5;
const unsigned int	kPIN__RESET							= 6;

//	----------------------------------------------------------------------------------------------------
//	Transition status

const unsigned int	kKEY_STATE__UNKEYED					=	false;
const unsigned int	kKEY_STATE__KEYED					=	true;

const unsigned int	kLOCK_STATE__UNLOCKED				=	false;
const unsigned int	kLOCK_STATE__LOCKED					=	true;

//	----------------------------------------------------------------------------------------------------
//	Pin levels

const unsigned int	kTUNE_START_PIN_STATE				=	HIGH;
const unsigned int	kSTART_IDLE_PIN_STATE				=	LOW;

const unsigned int	kLOCKED_PIN_STATE					=	HIGH;
const unsigned int	kUNLOCKED_PIN_STATE					=	LOW;

const unsigned int	kKEYED_PIN_STATE					=	HIGH;
const unsigned int	kUNKEYED_PIN_STATE					=	LOW;

const unsigned int	kTUNED_PIN_STATE					=	HIGH;
const unsigned int	kUNTUNED_PIN_STATE					=	LOW;

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

const unsigned int	kEVENT_QUEUE_SIZE					=	( 1 << 4 );		//	Keep this as a power of 2

const unsigned int	kASSERT_KEY_DELAY					=	  100;
const unsigned long int	kWATCHDOG_TIMEOUT				=	30000;

enum {
	kTUNING_STATES__IDLE = 0,
	kTUNING_STATES__UNLOCK,
	kTUNING_STATES__KEY,
	kTUNING_STATES__LOCK,
	kTUNING_STATES__UNKEY,
	kTUNING_STATES__NUM_STATES
} TUNING_STATES;

enum {
	kTUNING_STATE_EVENTS__NULL = 0,
	kTUNING_STATE_EVENTS__TUNE_REQEST_DETECTED,
	kTUNING_STATE_EVENTS__UNLOCKED_COMPLETED,
	kTUNING_STATE_EVENTS__250_MSEC_ELAPSED,
	kTUNING_STATE_EVENTS__KEY_COMPLETED,
	kTUNING_STATE_EVENTS__TUNED_STATUS_DETECTED,
	kTUNING_STATE_EVENTS__WATCHDOG_TIME_OUT,
	kTUNING_STATE_EVENTS__LOCKED_COMPLETED,
	kTUNING_STATE_EVENTS__UNKEY_COMPLETED,
	kTUNING_STATE_EVENTS__NUM_STATE_EVENTS
} TUNING_STATE_EVENTS;

typedef struct {
	unsigned int		tune_state;
	boolean				previousLockState;
	unsigned long int	masterTime;
	unsigned long int	watchdogTimer;
	unsigned int		keyDelayTimer;
	byte				eventQueue[kEVENT_QUEUE_SIZE];
	byte				eventQueueHead;
	byte				eventQueueTail;
	byte				eventQueueCount;
	boolean				previousTunedStatus;
	boolean				previousTuneStartState;
	boolean				previousKeyState;
	boolean				currentKeyState;
	boolean				currentLockState;
} ALL_GLOBALS;

ALL_GLOBALS			globals;

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

boolean enqueueEvent ( byte event )
{
	boolean result = false;
	
	if ( kEVENT_QUEUE_SIZE != globals.eventQueueCount )
	{
		globals.eventQueue[globals.eventQueueTail] = event;
		globals.eventQueueTail = ( globals.eventQueueTail + 1 ) % kEVENT_QUEUE_SIZE;
		globals.eventQueueCount++;
		result = true;
	}
}

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

boolean	dequeueEvent ( byte * event )
{
	boolean result = false;
	
	if ( 0 != globals.eventQueueCount )
	{
		*event = globals.eventQueue[globals.eventQueueHead];
		globals.eventQueueHead = ( globals.eventQueueHead + 1 ) % kEVENT_QUEUE_SIZE;
		globals.eventQueueCount--;
		result = true;
	}
	
	return result;
}

//  --------------------------------------------------------------------------------
//	Positive logic.  Returns true on a transition from not tuned to tuned.

boolean currentTunedStatus ( void )
{
	boolean currentState = ( kTUNE_START_PIN_STATE == digitalRead ( kPIN__TUNED_STATUS ) ) ? true : false ;
	boolean result = false;
	
	if ( globals.previousTunedStatus != currentState )
	{
		if ( currentState )
		{
			result = true;
		}
		
		globals.previousTunedStatus = currentState;
	}
	
	return result;
}

//  --------------------------------------------------------------------------------
//	Positive logic.  Returns true on transition from no tune request to tune start request.

boolean getTuneStartRequest ( void )
{
	boolean currentState = ( LOW == digitalRead ( kPIN__TUNE_START ) ) ? true : false ;
	boolean result = false;
	
	if ( globals.previousTuneStartState != currentState )
	{
		if ( currentState )
		{
			result = true;
		}
		
		globals.previousTuneStartState = currentState;
	}
	
	return result;
}

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

void setLockState ( int lockState )
{
	switch ( lockState )
	{
		case kLOCK_STATE__LOCKED:
		{
			digitalWrite ( kPIN__LOCK, kLOCKED_PIN_STATE );
			globals.currentLockState = kLOCKED_PIN_STATE;
			enqueueEvent ( kTUNING_STATE_EVENTS__LOCKED_COMPLETED );
		}
		break;
		
		case kLOCK_STATE__UNLOCKED:
		{
			digitalWrite ( kPIN__LOCK, kUNLOCKED_PIN_STATE );
			globals.currentLockState = kUNLOCKED_PIN_STATE;
			enqueueEvent ( kTUNING_STATE_EVENTS__UNLOCKED_COMPLETED );
		}
		break;
	}
}

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

void setKeyState ( int keyState )
{
	switch ( keyState )
	{
		case kKEY_STATE__KEYED:
		{
			digitalWrite ( kPIN__KEY, kKEYED_PIN_STATE );
			globals.currentKeyState = kKEYED_PIN_STATE;
			enqueueEvent ( kTUNING_STATE_EVENTS__KEY_COMPLETED );
		}
		break;
		
		case kKEY_STATE__UNKEYED:
		{
			digitalWrite ( kPIN__KEY, kUNKEYED_PIN_STATE );
			globals.currentKeyState = kUNKEYED_PIN_STATE;
			enqueueEvent ( kTUNING_STATE_EVENTS__UNKEY_COMPLETED );
		}
		break;
	}
}

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

void generateEvents ( void )
{
	if ( getTuneStartRequest () )
	{
		enqueueEvent ( kTUNING_STATE_EVENTS__TUNE_REQEST_DETECTED );
	}
	
	if ( currentTunedStatus () )
	{
		enqueueEvent ( kTUNING_STATE_EVENTS__TUNED_STATUS_DETECTED );
	}
	
	if ( 0 != globals.watchdogTimer )
	{
		globals.watchdogTimer--;
		if ( 0 == globals.watchdogTimer )
		{
			enqueueEvent ( kTUNING_STATE_EVENTS__WATCHDOG_TIME_OUT );
		}
	}
	
	if ( 0 != globals.keyDelayTimer )
	{
		globals.keyDelayTimer--;
		if ( 0 == globals.keyDelayTimer )
		{
			enqueueEvent ( kTUNING_STATE_EVENTS__250_MSEC_ELAPSED );
		}
	}
	
	if ( globals.previousKeyState != globals.currentKeyState )
	{
		if ( kKEY_STATE__KEYED == globals.currentKeyState )
		{
			enqueueEvent ( kTUNING_STATE_EVENTS__KEY_COMPLETED );
		}
		else if ( kKEY_STATE__UNKEYED == globals.currentKeyState )
		{
			enqueueEvent ( kTUNING_STATE_EVENTS__UNKEY_COMPLETED );
		}
		
		globals.previousKeyState = globals.currentKeyState;
	}
	
	if ( globals.previousLockState != globals.currentLockState )
	{
		if ( kLOCK_STATE__LOCKED == globals.currentLockState )
		{
			enqueueEvent ( kTUNING_STATE_EVENTS__LOCKED_COMPLETED );
		}
		else if ( kLOCK_STATE__UNLOCKED == globals.currentLockState )
		{
			enqueueEvent ( kTUNING_STATE_EVENTS__UNLOCKED_COMPLETED );
		}
		
		globals.previousLockState = globals.currentLockState;
	}
}

//  --------------------------------------------------------------------------------
//	On power up, the SG-230 initializes to bypass mode.  If powered down, the SG-230 
//	has all inductors in series with the antenna.  Since SG-230 power is passed 
//	through a dongle that contains this auto-locking device, the SG-230 and the 
//	auto-locking device are always powered at the same time.  The initial power-up
//	condition shall be to an un-tuned and un-locked state, to properly reflect the 
//	state of the tuner.

void setup ()
{
	pinMode ( kPIN__TUNE_START, INPUT_PULLUP );
	pinMode ( kPIN__KEY, OUTPUT );
	pinMode ( kPIN__LOCK, OUTPUT );
	pinMode ( kPIN__TUNED_STATUS, INPUT_PULLUP );
	pinMode ( kPIN__RESET, OUTPUT );
	
	digitalWrite ( kPIN__LOCK, kUNLOCKED_PIN_STATE );
	globals.tune_state = kTUNING_STATES__IDLE;
}

//  --------------------------------------------------------------------------------
//	Basic timing for emulating the Icom tuners has the radio issue a TUNE signal for 
//	500 mSec, and the tuner then asserts the key-line 250 mSec after the negation of 
//	the TUNE signal.
//			____                    _____________________________________
//	TUNE	    |___________________|
//			__________________________________
//	KEY		                                  |__________________________
//
//	A complete tune cycle, including status from the SG-230 tuner, might appear as 
//	follows:
//			____     ___________________________________________________________
//	TUNE*	    |___|
//			__________                       ___________________________________
//	KEY*	          |_____________________|
//
//			_____                          _____________________________________
//	LOCK	     |________________________|
//
//			____________                 _______________________________________
//	TUNED	      |_____|_______________|
//
//	This module will respond to initiation of a tuning cycle, as indicated by the
//	assertion of TUNE, by unlocking the tuner, and then asserting the KEY line to 
//	begin tuning.  While tuning, the TUNED signal is monitored to determine when 
//	tuning is completed, concurrent with running a watch-dog timer that is started 
//	when the tuning cycle is started.  If the TUNED cycle is asserted by the tuner, 
//	indicating successful tuning has been completed, this module will assert LOCk 
//	and then negate the KEY signal, leaving the tuner in an operational mode for 
//	the current frequency and antenna configuration.  If the watch-dog timer reaches
//	terminal count, the KEY line will be negated and the LOCK signal will remain 
//	negated.
//
//	Observed timing for a tuner that adheres to the Icom AH-4 tuner protocol, an 
//	Elecraft KPA500, has shown that the delay from the negation of TUNE until the 
//	tuner asserts the KEY line can vary from as little as 40 milliseconds, to as 
//	much as 500 milliseconds.  This module implements timing that is at the mid-point 
//	between the minimum and maximum observed timing.

void loop ()
{
    unsigned long int	currentTime = millis ();
	byte				event = kTUNING_STATE_EVENTS__NULL;
	boolean				stateUpdated = false;
    boolean				result = false;

    if ( globals.masterTime != currentTime )
    {
		globals.masterTime = currentTime;
		
		generateEvents ();
		if ( dequeueEvent ( &event ) )
		{
			switch ( globals.tune_state )
			{
				case kTUNING_STATES__IDLE:
				{
					switch ( event )
					{
						case kTUNING_STATE_EVENTS__TUNE_REQEST_DETECTED:
						{
							setLockState ( kLOCK_STATE__UNLOCKED );
							globals.tune_state = kTUNING_STATES__UNLOCK;
							globals.keyDelayTimer = kASSERT_KEY_DELAY;
							stateUpdated = true;
						}
						break;
					}
				}
				break;

				case kTUNING_STATES__UNLOCK:
				{
					switch ( event )
					{
						case kTUNING_STATE_EVENTS__UNLOCKED_COMPLETED:
						{
							setKeyState ( kKEY_STATE__KEYED );
							globals.tune_state = kTUNING_STATES__KEY;
							globals.watchdogTimer = kWATCHDOG_TIMEOUT;
							stateUpdated = true;
						}
						break;
					}
				}
				break;

				case kTUNING_STATES__KEY:
				{
					switch ( event )
					{
						case kTUNING_STATE_EVENTS__TUNED_STATUS_DETECTED:
						{
							globals.watchdogTimer = 0;
							setLockState ( kLOCK_STATE__LOCKED );
							globals.tune_state = kTUNING_STATES__LOCK;
							stateUpdated = true;
						}
						break;
					
						case kTUNING_STATE_EVENTS__WATCHDOG_TIME_OUT:
						{
							setKeyState ( kKEY_STATE__UNKEYED );
							globals.tune_state = kTUNING_STATES__UNKEY;
							stateUpdated = true;
						}
						break;
					}
				}
				break;

				case kTUNING_STATES__LOCK:
				{
					switch ( event )
					{
						case kTUNING_STATE_EVENTS__LOCKED_COMPLETED:
						{
							setKeyState ( kKEY_STATE__UNKEYED );
							globals.tune_state = kTUNING_STATES__UNKEY;
							stateUpdated = true;
						}
						break;
					}
				}
				break;

				case kTUNING_STATES__UNKEY:
				{
					switch ( event )
					{
						case kTUNING_STATE_EVENTS__UNKEY_COMPLETED:
						{
							globals.tune_state = kTUNING_STATES__IDLE;
							stateUpdated = true;
						}
						break;
					}
				}
				break;
			}
		}
	}
}