//************************************************************************
//**
//** Project.....: USB controller firmware for the Softrock 6.3 SDR,
//**                   enhanced with the 9V1AL Motherboard v 3.6, an
//**                   external LPF bank and other essentials to create
//**                   an all singing and all dancing HF SDR amateur
//**                   radio transceiver
//**
//** Platform.....:    ATmega168 @ 16.0 MHz
//**
//** Licence......:    This software is freely available for non-commercial 
//**                   use - i.e. for research and experimentation only!
//**
//** Copyright for USB routines: (c) 2006 by OBJECTIVE DEVELOPMENT Software GmbH
//**                   Based on ObDev's AVR USB driver by Christian Starkjohann
//**
//** History &
//** Acknowledgements: This firmware, although bloated up to three times its original
//**                   size to cater for the new functions included, is 100% based on
//**                   source code by F.W. Krom, PE0FKO. See:
//**                   http://home.ict.nl/~fredkrom/pe0fko/SR-V9-Si570/
//**
//**                   The code retains the original command structure
//**                   and is compatible with V 15.12 of the PE0FKO code.
//**                   Thanks Fred!!!
//**
//** 
//**                   Functionality highlights description:
//**
//**                    1) Control of the Si570 Programmable Crystal Oscillator,
//**                       including "smoothtune" for seamless tuning between small
//**					   frequency steps
//**                    2) Variable speed VFO, using a rotary encoder, and 9 short/long term
//**                       memories, using a pushbutton (cycle with short pushes, store with long)
//**					3) Accomodate different Encoder Resolutions (PPT) through a User command
//**                    4) CW Paddle inputs
//**                    5) PTT output
//**                    6) PTT2 output (connected to SWR protect function)
//**                    7) Automatic selection between 4 Bandpass filters,
//**                       with user selectable switchpoints
//**                    8) Automatic selection between 6 Lowpass filters, with user selectable
//**                       switchpoints, controls 3 GPIO output bits.
//**                    9) Measurement of Power forward and Power refleced 
//**                       (uses input from an external Power/SWR bridge)
//**                   10) Hi-SWR protect function, when using an external Power/SWR bridge
//**                       (assert an external signal, for instance to lower the PA output)
//**                   11) PEP indication for power output, shows the highest measured value within a
//**                       user adjustable time window of up to 2 seconds (10 samples per second).
//**                   12) 16x2 LCD display for Frequency readout and Power/SWR metering, using
//**                       a bargraph style display to emulate analog meters.
//**                   13) When switching to TX, Power bargraph display is delayed by 0.5s to enable
//**                       display of frequency change when switching to TX mode, using PowerSDR-IQ  
//**
//**                   Note that some of the items described above, such as the Rotary
//**                   Encoder VFO, the PTT2 output, the Power/SWR bridge and the LCD,
//**                   while not necessary, are optional features provided by the
//**				   firmware.  Further description may be found here:
//**                   http://sites.google.com/site/lofturj/softrock6_3
//**
//**
//** Initial version.: 2009-03-18, Loftur Jonasson, TF3LJ
//**
//**
//** This update:....: 2010-04-19 Version 1.03 & USB Protocol V. 16.2
//**
//*********************************************************************************



Note:  A substantial part of the text below is a copy/paste from Fred Krom's (PE0FKO) Readme.txt file
for his Firmware release version 15.12, developed to support the Softrock 9.0 receiver.
Descriptions of new and/or modified USB commands have been added.  2009-08-23...2009-10-23...
2010-01-08, 2010-04-15 TF3LJ


***********************************************************************************
*                                                                                 *
*              USB Protocol Description for the ATmega168 firmware                *
*             version 1.03,  implementing v 16.2 of the USB protocol              *
*                   (version number co-ordinated with PE0FKO)                     *
*                                                                                 *
*     This firmware implements a subset of the features available in the          *
*      AVR-Mobo 4.3 firmware (see http://sites.google.com/site/lofturj)           *
*                                                                                 *
*    This protocol, while including a number of extensions, is intended to be     *
*               compatible with the PE0FKO ATtiny45/80 code V15.12                *
*                                                                                 *
***********************************************************************************

Implemented functions:
----------------------

The 168 column below shows functions implemented in the ATmega168 firmware v1.03
For comparison, the LJ column below shows functions implemented in my Mobo 4.3
firmware v1.05, the FKO column shows the commands implemented by the PE0FKO
ATtiny 45/85 V 15.11 firmware, and the SAQ column shows commands implemented by
the DG8SAQ ATiny 45 V 1.4 firmware.


+----+---+---+---+---+---+-----------------------------------------------------
|Cmd |SAQ|FKO|168| LJ| IO| Function
+0x--+---+---+---+---+---+-----------------------------------------------------
| 00 | * |   |   |   | I | Echo value variable
| 00 |   | * | * | * | I | Get Firmware version number
| 01 | * | * | ++| +-| I | [OPTION] [enabled but do not use] PORTD, set port directions (use commands 0x15/16)
| 02 | * | * | ++| +-| I | [OPTION] [enabled but do not use] read port           (use commands 0x15/16)
| 03 | * | * | ++| +-| I | [OPTION] [enabled but do not use] read port states    (use commands 0x15/16)
| 04 | * | * | ++| +-| I | [OPTION] [enabled but do not use] set port outputs    (use commands 0x15/16)
| 05 | * |   |   |   | I | [DO NOT USE] send I2C start sequence
| 06 | * |   |   |   | I | [DO NOT USE] send I2C stop sequence
| 07 | * |   |   |   | I | [DO NOT USE] send byte to I2C
| 08 | * |   |   |   | I | [DO NOT USE] send word to I2C
| 09 | * |   |   |   | I | [DO NOT USE] send dword to I2C
| 0A | * |   |   |   | I | [DO NOT USE] send word to I2C with start and stop sequence
| 0B | * |   |   |   | I | [DO NOT USE] receive word from I2C with start and stop sequence
| 0C | * |   |   |   | I | [DO NOT USE] modify I2C clock
| 0D |   |   |   |   | I | [DO NOT USE] read OSCCAL to "value"
| 0E |   |   |   |   | I | [DO NOT USE] Write "value" to OSCCAL
| 0F | * | * | * | * | I | Reset uController
| 10 | * |   |   |   | I | [DO NOT USE] EEPROM write byte value=address, index=data
| 11 | * |   |   |   | I | [DO NOT USE] EEPROM read byte "value"=address
| 13 |   |   |   |   | I | [DO NOT USE] return usb device address
| 15 |   | * | + | +-| I | Set PORTD with mask and data bytes, and perform cmd 0x16
| 16 |   | * | + | +-| I | PORTD, return the I/O p-in value
| 17 |   | * | + | + | I | Read the BPF and LPF Filter cross over points (8+16 values) and set one point
| 18 |   | * | * | *-| I | Set the Band Pass Filter Address for one band: 0, 1, 2... 7 (3bit value)
| 19 |   | * | * | *-| I | Read the Band Pass Filter Addresses for bands 0, 1, 2... 7  (3bit value)
| 1a |   |   | + | +-| I | Set the TX Low Pass Filter Address for one LPF band: 0, 1, 2... 15 (4bit value)
| 1b |   |   | + | +-| I | Read the TX Low Pass Filter Addresses for LPF bands 0, 1, 2... 15  (4bit value)
| 20 | * | * | * | *-| I | Write byte to Si570 register
| 21 | * |   |   |   | I | [DO NOT USE] SI570: read byte to register index (Use command 0x3F)
| 22 | * |   |   |   | I | [DO NOT USE] SI570: freeze NCO (Use command 0x20)
| 23 | * |   |   |   | I | [DO NOT USE] SI570: unfreeze NCO (Use command 0x20)
| 30 | * | * | * | * | O | Set frequency by register and load Si570
| 31 |   | * | * |*+-| O | [OPTION] [normally disabled] Write the frequency/band subtract multiply to the eeprom (values for 4 separate bands)
| 32 | * | * | + | + | O | Set frequency by value and load Si570 (if Rotary Encoder VFO, then 10 frequencies accessible)
| 33 | * | * | * | * | O | write new crystal frequency to EEPROM and use it.
| 34 |   | * | + | + | O | Write new startup frequency to eeprom (if Rotary Encoder VFO, then 10 memories accessible)
| 35 |   | * | * | * | O | Write new smooth tune to eeprom and use it.
| 36 |   |   |   | +-| O | [Not available] Modify Rotary Encoder Resolution & LCD offset for PSDR-IQ during RX
| 39 |   | * | * |*+-| I | Return the frequency/band subtract multiply (values for 4 separate bands)
| 3A |   | * | * | + | I | Return running frequency
| 3B |   | * | * | * | I | Return smooth tune ppm value
| 3C |   | * | * | + | I | Return the startup frequency (if Rotary Encoder VFO, then returns the currently active memory)
| 3D |   | * | * | * | I | Return the XTal frequency
| 3E |   |   |   |   | I | [DEBUG] read out calculated frequency control registers
| 3F | * | * | * | * | I | Read out frequency control registers
| 40 | * | * | * | *-| I | Return I2C transmission error status
| 41 | * |   |   |   | I | [DO NOT USE] set/reset init freq status
| 41 |   | * | + | + | I | Read/Modify the I2C addresses (Si570, on and off-board PCF8574 devices, TMP100, AD5301, AD7991). Reset uController
| 43 |   | * | * |   | I | Change USB SerialNumber ID
| 50 | * | * | + | + | I | Set TX state and Read CW key inputs
| 51 | * | * | + | + | I | Read CW key inputs
| 60 |   |   | + |   | I | Feature Select (LCD display on/off, Rotary Encoder on/off)
| 61 |   |   | + | + | I | Read analog inputs (P_out & P_ref) (I_in, V_in and Tmp not available in ATmega168 version)
| 64 |   |   |   | + | I | [Not available] Read/Modify the PA High Temperature limit, Cooling Fan On/Off limits, and optionally select external PCF control bit
| 65 |   |   |   | + | I | [Not available] Read/Modify PA bias setting related values, 5 items 
| 66 |   |   | + | + | I | Read/Modify SWR measurement and SWR alarm related values 6 items 
| 67 |   |   |   | ++| I | Set the Encoder Resolvable States per Revolution for 1kHz tune per Rev
| 68 |   |   |   | ++| I | Display a fixed frequency offset during RX only.
| 6e |   |   |   | ++| I | [Not available] Write a Byte to (PCF8584) GPIO Extender 
| 6f |   |   |   | ++| I | [Not available] Read a Byte from (PCF8584) GPIO Extender  
| 7f |   |   | + |   | I | Direct commands to I2C connected LCD display (address change, contrast/brightness)

+:  New or modified function 
Second +/-:  Optional, + = Normally enabled, - = Normally disabled

[OPTION] indicates that this feature may be selected during compile time (#define Switch in _main.h)


Commands:
---------
All the commands are working with the "usb_control_msg" command from the LibUSB open source project.
See "libusb-win32-device-bin-0.1.12.1" from http://sourceforge.net/projects/libusb-win32/

To use the library, include the header file ./include/usb.h in your project and add the 
library ./lib/*/libusb.lib for your linker.

Open the device with usb_open(...) with the VID & PID to get a device handle.

  int usb_control_msg(usb_dev_handle *dev, int requesttype, int request,
                      int value, int index, char *bytes, int size,
                      int timeout);

    requesttype:    Data In or OUT command (table IO value)
        I = USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN
        O = USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT

    request: The command number.
    value:   Word parameter 0
    index:   Word parameter 1
    bytes:   Array data send to the device (OUT) or from the device (IN)
    size:    length in bytes of the "bytes" array.


In case of an unknown command, the firmware will return a 1 (one), if there is a bytes array specified it 
returns the byte 255.


In the next examples I will use two subroutines that will call the usb_control_msg function, it make's 
the examples more readable:

int usbCtrlMsgIN(int request, int value, int index, char *bytes, int size)
{
  return usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
                         request, value, index, bytes, size, 500);
}

int usbCtrlMsgOUT(int request, int value, int index, char *bytes, int size)
{
  return usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT,
                         request, value, index, bytes, size, 500);
}




Command 0x00:
-------------
This call will return the version number of the firmware. The high byte is the version major and the
low byte the version minor number.

It is a bid tricky for the previous versions because they used a "word echo command" on command 0x00.
If the call will be done with the "value" parameter is set to 0x0E00 it will return version 14.0 for the
original DG8SAQ software. (Also my previous software will return the version 14.0, the owner had to
upgrade or not use it). There is also a other way to check the type of software, use the USB Version 
string for that.

Code sample:
    uint16_t version;
    r = usbCtrlMsgIN(0x00, 0x0E00, 0, &version, sizeof(version));
	// if the return value is 2, the variable version will give the major and minor
	// version number in the high and low byte.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x00
    value:           0x0E00
    index:           Don't care
    bytes:           Version word variable
    size:            2


Command 0x01:
-------------
[OPTION] [enabled but do not use]
Set pin directions, PORT D.
Legacy command, use I/O function 0x15/0x16 instead


Command 0x02:
-------------
[OPTION] [enabled but do not use]
Read ports, PORT D.
Legacy command, use I/O function 0x15/0x16 instead


Command 0x03:
-------------
[OPTION] [enabled but do not use]
Read port states, PORTD.
Legacy command, use I/O function 0x15/0x16 instead


Command 0x04:
-------------
[OPTION] [enabled but do not use]
Set ports, PORTD.
Legacy command, use I/O function 0x15/0x16 instead


Command 0x0F:
-------------
Restart the board (done by Watchdog timer).

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x0F
    value:           Don't care
    index:           Don't care
    bytes:           NULL
    size:            0


Command 0x15:
-------------
Set the I/O bits of PORT D.
It also reads and returns the PORT D inputs (like command 0x16).

There are two values for every I/O bit, the data direction and data bits.
+-----+------+----------------------
| DDR | DATA | PIN Function
+-----+------+----------------------
|  0  |   0  | input
|  0  |   1  | input internal pullup
|  1  |   0  | output 0
|  1  |   1  | output 1
+-----+------+----------------------

In case of the enabled ABPF no change of I/O will be done.

Code sample:
    uint16_t INP;
    r = usbCtrlMsgIN(0x15, 0x02, 0x02, &INP, sizeof(INP));
    // Set P2 to output and one!
    // Use P1 as input, no internal pull up R enabled.
    // Read the input in array INP[], only bit0 and bit1 used by this hardware.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x15
    value:           Data Direction Register
    index:           Data register
    bytes:           PIN status (returned)
    size:            2


Command 0x16:
-------------
Read the PORT D inputs

Code sample:
    uint16_t INP;
    r = usbCtrlMsgIN(0x16, 0, 0, &INP, sizeof(INP));
    // Read the input word INP, only bit0 and bit1 used by SoftRock V9 hardware.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x16
    value:           Don't care
    index:           Don't care
    bytes:           PIN status (returned)
    size:            2


Command 0x17:
-------------
Read the Filter cross over points and set one point.

This command can control 2 banks of filters.  Typically the first bank is the Rx and Tx BPF
used in the QSD and QSE stages.  The second bank is the Tx LPF between the PA and the antenna
output.  The # of crossover points of the two banks of filters can be different.  For example,
the first bank is usually 4 bands (160m, 80/40m, 30/17/20m, 15/12/10m) as in Softrock v6.3 and
v9.0.  The second bank is usually 6 bands but can go up to 7, 8, 12 or even 16!

For the ATmega168 firmware the first bank is 4 bands (e.g. 160m, 80m+40m, 30m+20m+17m, 15m+12m+10m)
For the ATmega168 firmware the second bank is 6 bands (e.g. 160m, 80m, 40m, 30m+20m, 17m+15m, 12m+10m)

The index is used to specify the particular filter crossover point.  The index of the 1st bank
starts from 0, and ends at the last crossover point.  For example, if there are 4 bands, then
there will be 3 crossover points: 0, 1, and 2.  Index 3 is used as a boolean flag, specifying
whether this filter bank is enabled or disabled (for automatic band pass filter ABPF function).

The index of the 2nd filter bank starts from 256, and ends at the last crossover point, which
in case of the ATmega168 firmware is 260.  For example, if there are 6 LPF's, then there will be
5 crossover points, with index 256, 257, 258, 259, and 260.  The higher, unused crossover points
would typically be set with the same or a higher value than the one in 260.  The last index, or
index 261 is used as a boolean flag, specifying whether this filter bank is enabled or disabled
(for automatic switching).  If "disabled", it usually means the filter is set for "all pass" or
bypassed.

The first call to this command should be used to find out how many crossover points there are.
Call with an index of 255 for the 1st filter bank, and an index of 256+255 for the 2nd filter
bank.

  filter_number_of_bytes = usbCtrlMsgIN(0x17, 0, 255, FilterCrossOver, sizeof(FilterCrossOver));

If there are 4 filters (3 crossover points), then the filter_number_of_bytes returned will be 8.
(Each crossover point is 2 bytes.  Thus there will be 6 bytes.  Following that another 2 bytes will
be for the boolean flag, making a total of 8).

If ther are 6 filters (5 crossover points), there filter_number_of_bytes returned will be 12.

Subsequent calls to this command can then be used to:

1.  set one of the filter crossover points by specifying the index
2.  enable/disable the filter bank by specifying the last index (for the boolean flag)
3.  read the cross over points only - all of them of one bank at once, by specifying 255 (1st bank)
    or 256+255 (2nd bank).

(Note that in actions 1 and 2 above, the cross over points are also read out after the completion
of the action.)

The data format of the crossover points is a 11.5 bits in MHz, that gives a resolution of 1/32 MHz.
The last data entry is a boolean flag to enable or disable the filter bank.

Code sample:
   uint16_t FilterCrossOver[16];        // allocate enough space for up to 16 filters
   unsigned int filter_number_of_bytes;

  // first find out how may cross over points there are for the 1st bank, use 255 for index
  filter_number_of_bytes = usbCtrlMsgIN(0x17, 0, 255, FilterCrossOver, sizeof(FilterCrossOver));

  // Specify filter cross over point for a softrock that divide the LO by 4!
  // And read the points back from the device in the last call.
  if (filter_number_of_bytes == 8)  // 3 crossover points and one flag, so set them up
  {
	FilterCrossOver[0] = 4.1 * 4.0 * (1<<5);
	FilterCrossOver[1] = 8.0 * 4.0 * (1<<5);
	FilterCrossOver[2] = 16. * 4.0 * (1<<5);
	FilterCrossOver[3] = true;        // Enable

	usbCtrlMsgIN(0x17, FilterCrossOver[0], 0, NULL, 0);
	usbCtrlMsgIN(0x17, FilterCrossOver[1], 1, NULL, 0);
	usbCtrlMsgIN(0x17, FilterCrossOver[2], 2, NULL, 0);
	usbCtrlMsgIN(0x17, FilterCrossOver[3], 3, FilterCrossOver, sizeof(FilterCrossOver));
  }


Parameters: Setting one of the points
   requesttype:    USB_ENDPOINT_IN
   request:         0x17
   value:           FilterCrossOver[i]   i being the index of the particular cross over point
   index:           index of the 'value' filter point.
   bytes:           Array of up to 16 16bits integers for the filter points.
   size:            filter_number_of_bytes

Parameters: Enable / disable the filter
   requesttype:    USB_ENDPOINT_IN
   request:         0x17
   value:           0 (disable) or 1 (enable)
   index:           index of 1 plus the last crossover point
   bytes:           Array of up to 16 16bits integers for the filter points.
   size:            filter_number_of_bytes


Command 0x18:
-------------
Set the Band Pass Filter Address for one band: 0, 1, 2... 7 (3 bit value)
Returns all 8 BPF address values
Note that the Mobo 4.3 only has 3 bits available for BPF Address output,
higher bits are masked out.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x18
    value:           New Input Address Byte Value  (3 bit values)
    index:           Frequency Band 0 - 7 for new Input Address Byte value
    bytes:           pointer to 8 bytes, containing all BPF address values  (3 bit values)
    size:            8


Command 0x19:
-------------
Read the Band Pass Filter Addresses for bands 0, 1, 2... 7 (3 bit value)
Returns all 8 BPF address values
Note that the Mobo 4.3 only has 3 bits available for BPF Address output,
higher bits are masked out.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x19
    value:           Don't care
    index:           Don't care
    bytes:           pointer to 8 bytes, containing all BPF address values (3 bit values)
    size:            8


Command 0x1a:
-------------
Set the External Filter (MegaFilter Mobo) Address for one band: 0, 1, 2... 15 (4bit value)
Returns all 16 External filter values


Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x1a
    value:           New Input Address Byte Value (8 bit values)
    index:           Frequency Band 0 - 15 for new Input Address Byte value
    bytes:           pointer to 16 bytes, containing all LPF address values (4 bit values)
    size:            16


Command 0x1b:
-------------
Read the TX Low Pass Filter Addresses for LPF bands 0, 1, 2... 15 (4bit value)
Returns all 16 LPF address values


Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x1b
    value:           Don't care
    index:           Don't care
    bytes:           pointer to 8 bytes, containing all LPF address values (4 bit values)
    size:            16


Command 0x20:
-------------
Write one byte to a Si570 register. Return value is the i2c error boolean in the buffer array.

Code sample:
	// Si570 RECALL function
	uint8_t i2cError;
    r = usbCtrlMsgIN(0x20, 0x55 | (135<<8), 0x01, &i2cError, 1);
	if (r == 1 && i2cError == 0)
		// OK

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x20
    value:           I2C Address low byte (only for the DG8SAQ firmware)
                     Si570 register high byte
    index:           Register value low byte
    bytes:           NULL
    size:            0


Command 0x30:
-------------
Set the oscillator frequency by Si570 register. The real frequency will be 
calculated by the firmware and the called command 0x32

Default:    None

Parameters:
    requesttype:    USB_ENDPOINT_OUT
    request:         0x30
    value:           I2C Address (only for the DG8SAQ firmware), Don't care
    index:           7 (only for the DG8SAQ firmware), Don't care
    bytes:           pointer 48 bits register
    size:            6


Command 0x31:
-------------
Write the frequency subtract, multiply value's to the eeprom and use it (values for 4 separate bands).

The real frequency is the input frequnecy minus the subtract value times the multiply value.
Si570_F = (Finput - subtract) * multiply


Default:    None

Parameters:
    requesttype:    USB_ENDPOINT_OUT
    request:         0x31
    value:           Don't care
    index:           Band 0 - 3.  Corresponds to the BPF filter bands
    bytes:           pointer 2 * 32 bits interger
    size:            8

Code sample:
	double sub, mul;
    uint32_t iSM[2];

	sub = 135.0;
	mul = 4.0;

	iSM[0] = (uint32_t)( sub * (1UL << 21) );
	iSM[1] = (uint32_t)( mul * (1UL << 21) );

    r = usbCtrlMsgOUT(0x31, 0, 0, (char *)iSM, sizeof(iSM));
    if (r != sizeof(iSM)) Error


Command 0x32:
-------------
Set the oscillator frequency by value. The frequency is formatted in MHz
as 11.21 bits value. 
The "automatic band pass filter selection", "smooth tune", "one side calibration" and
the "frequency subtract multiply" are all done in this function. (if anabled in the firmware)

If the Rotary Encoder Option has been enabled, then the Index selects memory location 0 - 9,
into which the frequency is written.  Memory location 0 is the active location, a write to 0
results in an instant change to the new frequency.  Memory locations 1 - 9 normally store the
last frequency used in each of the bands {1.8, 3.5, 7, 10, 14, 18, 21, 24, 28} MHz.
Index 1 - 9 gives access to overwrite those memory locations with a new value, without regard
to frequency band order.  A write to any of these memory locations will not be stored in
EEPROM.

Default:    None

Parameters:
    requesttype:    USB_ENDPOINT_OUT
    request:         0x32
    value:           Don't care
    index:           Don't care
    bytes:           pointer 32 bits integer
    size:            4

Code sample:
    uint32_t iFreq;
    double   dFreq;

    dFreq = 30.123456; // MHz
    iFreq = (uint32_t)( dFreq * (1UL << 21) )
    r = usbCtrlMsgOUT(0x32, 0, 0, (char *)&iFreq, sizeof(iFreq));
    if (r < 0) Error


Command 0x33:
-------------
Write new crystal frequency to EEPROM and use it. It can be changed to calibrate the device.
The frequency is formatted in MHz as a 8.24 bits value.

Default:    114.285 MHz

Parameters:
    requesttype:    USB_ENDPOINT_OUT
    request:         0x33
    value:           Don't care
    index:           Don't care
    bytes:           pointer 32 bits integer
    size:            4

Code sample:
    uint32_t iXtalFreq;
    double   dXtalFreq;

    dXtalFreq = 114.281;
    iXtalFreq = (uint32_t)( dXtalFreq * (1UL<<24) )
    r = usbCtrlMsgOUT(0x33, 0, 0, (char *)&iXtalFreq, sizeof(iXtalFreq));
    if (r < 0) Error


Command 0x34:
-------------
Write new startup frequency to eeprom. When the device is started it will output
this frequency until a program set an other frequency.
The frequency is formatted in MHz as a 11.21 bits value.

If the Rotary Encoder Option has been enabled, then the Index selects memory location 0 - 9,
into which the frequency is written.  The memory location written to will be selected as
the startup frequency when the controller is powered up, unless changed by a Rotary Encoder
"Memory Store" action.  Memory location 0 cannot be accessed by the Rotary Encoder "Memory Rotate"
function.  Memory locations 1 - 9 store the last frequency saved, normally in each of the bands
{1.8, 3.5, 7, 10, 14, 18, 21, 24, 28} MHz.  Each memory location can be written to any frequency,
regardless of the frequency band scheme indicated above.  A write to any of these memory
locations is stored in EEPROM.

Default:    7.050 MHz

or with Rotary Encoder Option enabled:
 	    7.050 MHz
	    1.820 MHz
	    3.052 MHz
	    7.020 MHz
	   10.120 MHz
	   14.020 MHz
	   18.090 MHz
	   21.020 MHz
	   24.910 MHz
	   28.020 MHz

Parameters:
    requesttype:    USB_ENDPOINT_OUT
    request:         0x34
    value:           Don't care
    index:           Don't care
    bytes:           pointer 32 bits integer
    size:            4

Code sample:
    uint32_t iXtalFreq;
    double   dXtalFreq;

    dFreq = 4.0 * 3.550; // MHz
    iFreq = (uint32_t)( dFreq * (1UL<<24) )
    r = usbCtrlMsgOUT(0x34, 0, 0, (char *)&iFreq, sizeof(iFreq));
    if (r < 0) Error


Command 0x35:
-------------
Write new smooth tune to eeprom and use it.

Default:    3500 PPM

Parameters:
    requesttype:    USB_ENDPOINT_OUT
    request:         0x35
    value:           Don't care
    index:           Don't care
    bytes:           pointer 16 bits integer
    size:            2

Code sample:
    uint16_t Smooth;
    Smooth = 3400;
    r = usbCtrlMsgOUT(0x35, 0, 0, (char *)&Smooth, sizeof(Smooth));
    if (r < 0) Error


Command 0x36:
-------------
[Not available in this firmware, legacy, use Cmd 0x67 & 0x68 instead]: 
Modify Rotary Encoder Resolution (useful if the Rotary Encoder has been enabled)
This value is a signed 32bit integer which contains frequency increments in units of ~0.12 Hz 
(or 8 ~ 1 Hz).  Note that if this value is set too high, or more than 3500ppm of the operating
frequency, then "smoothtune" breaks.  This value cannot be read

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x36
    value:           Don't care
    index:           Don't care
    bytes:           pointer to a signed 32 bits integer
    size:            4

Code sample:
    int32_t Enc_Resolution;
    Enc_Resolution = 80;
    r = usbCtrlMsgOUT(0x36, 0, 0, (char *)&Enc_Resolution, sizeof(Enc_Resolution));
    if (r < 0) Error


Command 0x39:
-------------
Return the frequency subtract multiply values (values for 4 separate bands).


Default:    subtract = 0.0, multiply = 1.0

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x39
    value:           Don't care
    index:           Band 0 - 3.  Corresponds to the BPF filter bands
    bytes:           pointer 2 * 32 bits integer
    size:            8

Code sample:
    uint32_t iSM[2];
	double sub, mul;

    r = usbCtrlMsgIN(0x39, 0, 0, (char *)iSM, sizeof(iSM));
    if (r != sizeof(iSM)) Error

	sub = (double)(int32_t)iSM[0] / (1UL << 21); // Signed value
	mul = (double)         iSM[1] / (1UL << 21);


Command 0x3A:
-------------
Return actual frequency of the device.
The frequency is formatted in MHz as a 11.21 bits value.

[Not available in the ATmega168 firmware]: 
If the Rotary Encoder Option has been enabled, then the Index selects memory location 0 - 9,
where memory location 0 contains the active frequency, and memory locations 1 - 9 contain
the 9 last used frequencies (one of which is the same as the running frequency)
Normally each of the nine memories correspond to the frequency bands:
{1.8, 3.5, 7, 10, 14, 18, 21, 24, 28}, but in practice, each memory location can be written to
any frequency, regardless of this frequency band scheme.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x3A
    value:           Don't care
    index:           Don't care
    bytes:           pointer 32 bits integer
    size:            4

Code sample:
    uint32_t iFreq;
    double   dFreq;
    r = usbCtrlMsgIN(0x3A, 0, 0, (char *)&iFreq, sizeof(iFreq));
    if (r == 4)
        dFreq = (double)iFreq / (1UL<<21);


Command 0x3B:
-------------
Return the "Smooth tune" PPM (pulse per MHz) of the device.
The value is default 3500 (from data sheet) and can be changed. I do not know what 
happened with the chip if it is out of range (>3500).
If the value is set to zero it will disable the "Automatic Smooth tune" function.

Default:    3500 PPM

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x3B
    value:           Don't care
    index:           Don't care
    bytes:           pointer 16 bits integer
    size:            2

Code sample:
    uint16_t Smooth;
    r = usbCtrlMsgIN(0x3B, 0, 0, (char *)&Smooth, sizeof(Smooth));
    if (r == 2) ...


Command 0x3C:
-------------
Return device startup frequency.
The frequency is formatted in MHz as a 11.21 bits value.

[Not available in the ATmega168 firmware]: 
If the Rotary Encoder Option has been enabled, then the Index selects EEPROM location 0 - 9,
where EEPROM location 0 contains the startup frequency and EEPROM locations 1 - 9 contain
the 9 last stored frequencies (one of which is the same as the startup frequency)
Normally each of the nine memories correspond to the frequency bands:
{1.8, 3.5, 7, 10, 14, 18, 21, 24, 28}, but in practice, each memory location can be written to
any frequency, regardless of this frequency band scheme.

Default:    7.050 MHz

or with Rotary Encoder Option enabled:
 	    7.050 MHz
	    1.820 MHz
	    3.052 MHz
	    7.020 MHz
	   10.120 MHz
	   14.020 MHz
	   18.090 MHz
	   21.020 MHz
	   24.910 MHz
	   28.020 MHz

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x3C
    value:           Don't care
    index:           Don't care
    bytes:           pointer 32 bits integer
    size:            4

Code sample:
    uint32_t iFreq;
    double   dFreq;
    r = usbCtrlMsgIN(0x3C, 0, 0, (char *)&iFreq, sizeof(iFreq));
    if (r == 4)
        dFreq = (double)iFreq / (1UL<<21);


Command 0x3D:
-------------
Return device crystal frequency.
The frequency is formatted in MHz as a 8.24 bits value.

Default:    114.285 MHz

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x3D
    value:           Don't care
    index:           Don't care
    bytes:           pointer 32 bits integer
    size:            4

Code sample:
    uint32_t iFreqXtal;
    double   dFreqXtal;
    r = usbCtrlMsgIN(0x3D, 0, 0, (char *)&iFreqXtal, sizeof(iFreqXtal));
    if (r == 4)
        dFreqXtal = (double)iFreqXtal / (1UL<<24);


Command 0x3F:
-------------
Return the Si570 frequency control registers (reg 7 .. 12). If there are I2C errors
the return length is 0.

Default:    None

Parameters:
    requesttype:     USB_ENDPOINT_IN
    request:         0x3F
    value:           Don't care
    index:           Don't care
    bytes:           pointer 6 byte register array
    size:            6


Command 0x41:
-------------
Read/Modify the I2C addresses of all connected I2C devices.
The function can also be used to reset the microcontroller to "factory default" by writing the
value 255. After a restart the device will initialize to all the default values.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x41
    value:           I2C address or 255
    index:           see below
    bytes:           pointer 8 bits integer
    size:            1; unless "Factory Reset" by value 255, then size = 0, nothing returned

	Index 0 reads/modifies the Si570_I2C_addr (Default: 0x55 (85 decimal))
	Index 15 reads/modifies the address for the I2C connected LCD display (Default: 0x18)

	[Not available in the ATmega168 firmware]: 
	Index 1 reads/modifies the I2C address for the onboard PCF8574
	Index 2 reads/modifies the I2C address for the first PCF8574 used in the LPF Mobo
	Index 3 reads/modifies the I2C address for the second PCF8574 used in the LPF Mobo
	Index 4 reads/modifies the I2C address for the onboard TMP100 temperature sensor
	Index 5 reads/modifies the I2C address for the onboard AD5301 8 bit DAC
	Index 6 reads/modifies the I2C address for the onboard AD7991 4 x ADC
	Index 7 reads/modifies the I2C address for the external PCF8574 used for FAN, attenuators etc


Command 0x43:
-------------
Set and return the USB SerialNumber ID. The USB SerialNumber "PE0FKO-0" can be changed only for the
last char "0". If the value is not zero the ID char will be writen to eeprom and the old value is always returned,

Parameters:
	requesttype: USB_ENDPOINT_IN
	request: 0x43
	value: New ID char (>0) [byte] (ASCII, 0x30 = '0')
	index: 0
	bytes: pointer 1 byte ID address
	size: 1


Command 0x50:
-------------
Set the PTT I/O line and read CW key level from the PB5 (CW Key_1) and PB1 (CW Key_2).
The returnd bit value is bit 5 (0x20) for CW key_1 and bit 1 (0x02) for CW key_2, the other bits are zero.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x50
    value:           Output bool to user output PTT
    index:           0
    bytes:           pointer to 1 byte variable CW Key's
    size:            1


Command 0x51:
-------------
Read CW key level from the PB5 (CW Key_1) and PB1 (CW Key_2).
The returnd bit value is bit 5 (0x20) for CW key_1 and bit 1 (0x02) for CW key_2, the other bits are zero.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x51
    value:           0
    index:           0
    bytes:           pointer to 1 byte variable CW Key's
    size:            1


Command 0x60:
-------------
Select active features.
This command can be used to select or determine which optional features have been selected.
Currently, the available features are I2C connected LCD display and a Rotary Encoder driven VFO.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x60
    value:           Features_Register
    index:           Don't care
    bytes:           pointer to 1 byte containing the Features_Register
    size:            1

	The Features register is one byte used as 8 on/off switches.
	Bit 0 = LCD Display    (1 = enabled)
	Bit 1 = Rotary Encoder (1 = enabled)


Command 0x61:
-------------
Read analog inputs: 

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x61
    value:           Don't care
    index:           see below
    bytes:           pointer 16 bits integer
    size:            2

	Index 1 = Power Output
	Index 2 = Power reflected

	[Not available in the ATmega168 firmware]: 
	Index 0 = PA current, full scale 0xfff0 = 2.5A
	Index 3 = Supply voltage, full scale 0xfff0 = 15.64V
	Index 4 = Temperature in degC.Signed Int.  0 = 0 deg C
	          0x7ff0 =  127.9375 deg C (128*0x7ff0/0x8000), 0xfff0 = -128 deg C


Command 0x64:
-------------
[Not available in the ATmega168 firmware]: 
Read/Modify the PA High Temperature limit (deg C), PA Fan On trigger point (deg C),
PA Fan Off trigger point (deg C), and, optionally, if external PCF is configured for
this control: Which bit is used to control the Cooling Fan

If a Value contains 0, then the current active setting is read. If higher, then the active
setting is modified with the new value.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x64
    value:           PA temperature limit in deg C (1 - 127)
    index:           Don't care
    bytes:           pointer 8 bits integer
    size:            1

	Index 0 = PA High Temperature limit (deg C)
	Index 1 = PA Fan On trigger point (deg C)
	Index 2 = PA Fan Off trigger point (deg C)
	Index 3 = Which bit is used to control the Cooling Fan

Command 0x65:
-------------
[Not available in the ATmega168 firmware]: 
Read/Modify PA Bias Setting related values
If value contains 0, then the parameter is read, else it is written
By default Index 0 contains a 2, which selects the calibration value for the higher bias
setting.  This value is stored under index 4.
If Index 0 is set to 1, this selects the calibration value for the lower bias setting.
This valuse is stored under Index 3.
Index 0 set to any other value forces a Autobias function to run, cranking up the RD06HHF1
bias in increments of 1 and then comparing the PA_current with the values stored under Index 1
(for the lower bias setting) and Index 2 (for the higher bias setting).  When a match is found,
the value is stored under indexes 3 and 4 respectively.  If autobias function was unsuccessful,
a 0 is stored under indexes 3 and 4.
Note that if the higher bias setting is selected, and the SWR protect function is activated due
to a high SWR situation, the bias is automatically reduced to the lower setting while the SWR
Alarm remains active.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x65
    value:           8 bit parameter
    index:           see below
    bytes:           pointer 8 bits integer
    size:            1

	Index 0: Bias_Select; 0xff = Force Calibrate, 1 = LO, 2 = HI
	Index 1: PA Bias in 10 * mA, typically 2 = 20ma = Class B
	Index 2: PA Bias in 10 * mA, typically 35 = 350ma = Class A
	Index 3: PA Bias setting, LO  (normally an auto adjusted value)
	Index 4: PA Bias setting, HI  (normally an auto adjusted value)


Command 0x66:
-------------
Read/Modify Power and SWR Alarm Function related parameters
If value contains 0, then the parameter is read, else it is written

See description of Command 0x65 for SWR Alarm functionality.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x66
    value:           16 bit parameter
    index:           see below
    bytes:           Pointer 16 bits integer.  Index 4, 5 only use 8 bits
    size:            2

	Index 0 selects a trigger point value in milliWatts, to indicate a valid Power output measurement
	to be used for the SWR Alarm function.  If the measured Power output value is below this
	trigger point, then the SWR Alarm function remains inactive.  Smallest meaningful falue is appr 45mW,
	largest value is 65535mW.
	
	Index 1 value is used for a "Latency" timer (in units of 10ms, can be set awfully long:)
	If SWR alarm situation, and SWR is good again, then this timer has to expire before
	reverting back to full Power Out.
	
	Index 2 value decides the SWR Alarm threshold (27 equals an SWR of 2.7)
	
	Index 3 is a calibration value for the Power Out display.  Nominal value is 1000
	(set in AVR-Mobo.h)

	Index 4, enabled in the Bargraph LCD version, selects the Fullscale range for the Power
    Bargraph.  Default value (defined in AVR-Mobo.h) is 4, as in 4 Watts full scale.

	Index 5, selects the Fullscale range for the SWR	Bargraph.  Default value 
    (defined in AVR-Mobo.h) is 4, which equals an SWR of 5.0
	(Max SWR = Value + 1, or 4 = SWR of 5.0)

	Index 6: Number of PEP measurement samples for LCD power display (1-20).  A value of 1 effectively
	turns the PEP sampling off.  Default value (defined in AVR-Mobo.h) is 20, which equals a sample
	period of 2 seconds


Command 0x67:
-------------
Set the Encoder Resolvable States per Revolution for 1kHz tune per Rev
If value contains 0, then the parameter is read, else it is written
By default, the parameter is 1024 ( = 256 PPS)

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x67
    value:           16 bit value containing the Encoder PPS x 4 (resolvable states per revolution)
    index:           don't care
    bytes:           pointer 16 bits integer 
    size:            2


Command 0x68:
-------------
LCD Frequency Readout Offset for PowerSDR-IQ, during Receive Only.
This value is normally set as 0 for no offset.  Signed 8 bit integer, resolution 1kHz.
E.g. 9 = 9kHz, 246 = -9kHz.  Standard offset for PowerSDR-IQ is 9kHz.
This command uses the Index to indicate whether an input Value is to be read.  This
is necessary, as Value can be equal to 0.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x68
    value:           8 bit parameter
    index:           if Index>0, then New value contained in Value, else just read
    bytes:           pointer 8 bits signed integer
    size:            1


Command 0x6e:
-------------
[Not available in the ATmega168 firmware]: 
Write byte to PCF8574 I2C connected GPIO Extender.  The returned value is the actual
value read from the device

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x6e
    value:           Output byte
    index:           I2C address of device
    bytes:           pointer 8 bits integer
    size:            1


Command 0x6f:
-------------
[Not available in the ATmega168 firmware]: 
Read byte from PCF8574 I2C connected GPIO Extender.  The returned value is the actual
value read from the device

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x6f
    value:           Don't care
    index:           I2C address of device
    bytes:           pointer 8 bits integer
    size:            1


Command 0x7f:
-------------
Send 2 byte Commands to the I2C connected LCD display.
This command can be used to select or determine which optional features have been selected.
Currently, the available features are I2C connected LCD display and a Rotary Encoder driven VFO.

Parameters:
    requesttype:    USB_ENDPOINT_IN
    request:         0x60
    value:           Control_value
    index:           Command
    bytes:           NULL
    size:            0

	Examples of useful commands are:
	LCD_I2C_SET_CONTRAST	0x52, takes one byte arg: [contrast]
	LCD_I2C_SET_BRIGHTNESS	0x53, takes one byte arg: [brightness]
	LCD_I2C_CHG_ADDRESS		0x72, takes one byte arg: [new I2C address]	

	Note that if this Command is used to change the I2C address of the LCD display, then the
	address needs to be modified through Command 0x41 afterwards, to reconnect to the display.

EOF

