8.0 Information for Software developers

Here i plan to offer some information for developers of programs using the CI-V protocol. I have written myself many programs for various rigs using Basic, Visual Basic, Delphi and in the last years PERL.

I have no more sources for Basic and Visual basic, but many for Delphi and some for PERL. Feel free to send my an e-mail (see contact details at 'Impressum' (Imprint)).

If you have anything to contribute, i am most grateful, especially for languages like Visual Basic, C, C++, Visual C, since i don't use these. Please see 'Impressum' (Imprint) for contact details.

8.1 Delphi

To get CI-V to work, you have to control the serial interface of your PC. There are a lot of components (free and others) for Delphi which help you in doing that, look at the usual Delphi Pages (torry, DSP etc.). I personally use TSerial4 by R.Crowther and recommend it, although it's not free.

Serial comms happen with 8 bit, no parity, one stopbit, speed varies from 1200 to 19200 bps. Formerly 1200 bps was the normal speed for CI-V, but with most modern ICOM rigs you can set it to any other speed. As with many things - the faster the better.

Once you have serial comms up and running, you have to wait for incoming bytes. Each telegram of the CI-V protocol is in the same format, it begins with two '$FE' characters (bytes) and ends with one '$FD' byte. So you could just wait for an $FD and then work on the telegram. But there are pitfalls in this approach.

Since the CI-V line is a bus with perhaps several radios connected, collisions can occur (multiple radios sending data at once). In case of an collision a rig sends out the '$FC' character several times. On your serial port you might get a framing or other errors. So you have to check the received telegram for validity.

Here is how I do it (there are other ways, mine isn't probably the best and certainly not the only way to do it, but i found it to work very reliably):
I use a so called 'state machine', this is a routine which gets called each time one byte is received on the serial port. The state machine uses one variable, the state machine 'state'. This variable is used in a 'case' statement and selects the following execution path. Initially this is set to 'WaitPreamble1'.
Once i have received the first $FE i set the state to 'WaitPreamble2'. The next character comes in, the case branches to 'WaitPreamble2'. If this character is again a $FE the state variable is set to 'WaitToAdress', nothing else.
When the next byte is received the case branches to 'WaitToAdress', the byte is validated if it is a possible adress, if yes the state is advanced to 'WaitFromAdress'. If not, the state is set back to start, 'WaitPreamble1', ignoring the rest of the telegram.
This checking for errors on each stage is critical in ensuring that a valid frame (telegram) is received before acting on it.
After going through stages 'WaitPreamble1', 'WaitPreamble2', 'WaitToAdress' and 'WaitFromAdress' I set up a buffer (a record) which will hold the incoming message. In the following stages this buffer is filled (command, subcommand, data etc.) and, once it is complete, handed to a routine which processes the telegram (read out and display frequency information for example).

Perhaps it's best to show some code here:

procedure Readbyte(b: byte);
begin
  case state of
    WaitPreamble1: if b=$FE then state:=WaitPreamble2; //nothing else expected

    WaitPreamble2: if b=$FE
                      then state:=WaitToAdress    //advance state
                      else state:=WaitPreamble2;  //not what i expected, back to start

    WaitToAdress:  if (b=$E0) or (b=$00) //adressed to me ($E0) or to all ($00)
                      then state:=WaitFmAdress   //ok
                      else state:=WaitPreamble1; //not for me, back to square one

    WaitFmAdress:  if b=TheRigIwantToTalkTo //comes from the correct radio?
                      then begin //yes
                           incoming:=New(ptrIcomTelegram);
                           incoming.from:=b; //remember where it came from
                           incoming.datalength:=0;
                           state:=WaitCommand;
                           end
                      else state:=WaitPreamble1; //not the radio i expect

    WaitCommand:  if b<$1F //valid command byte?
                     then begin
                          incoming.cmd:=b;
                          state:=WaitFinal;
                          end
                     else begin
                          Dispose(incoming); //discard what we started earlier
                          state:=WaitPreamble1; //start over
                          end;

    WaitFinal: if b=$FD //is telegram complete ?
                  then begin
                       ProcessTelegram(incoming); //will dispose the buffer
                       state:=WaitPreamble1;
                       end
                  else begin
                       incoming.data[incoming.datalength]:=b;
                       inc(incoming.datalength);
                       //no state change here, continue to wait.
                       //you could check for max length or other errors.
                       end;
        end; //case
end;

The variables 'state' and 'incoming' are declared global in the unit.
One could add more error checking at each stage, e.g. look for framing errors from the serial port in case of a collision, or check if character '$FC' was received at any point. If an error occurs, discard what you received so far and start over.

There are some pitfalls here... e.g. what happens if a new telegram arrives before processing of the old has finished? For that reason I put the received telegram in a queue, the "ProcessTelegram" routine then checks the queue and works on the topmost (oldest) telegram only and finally disposes it. With the queue it can take all the time it wants for processing.
Other problems can arise from this approach, for example race conditions in managing the queue, but this example here is just to show the basic principles.

9.2 Visual Basic

As I don't use VB myself, I am depending on others here.
Wayne was so kind and provide an example, complete for VB6. Many thanks, Wayne! He writes:


3/19/02 by WRS

Another example or R7100 programming.

Contains an Auto-ID routine for automatically determining the Receiver ID
if you have correctly selected the Baud Rate and the Com Port. You however
must save the configuration and exit the program and restart it before the
ID change will take effect.

It should work on any ICOM however I only tested it on the R-75 and the
R-7100. My R-7100 is set as ID 14 Hex 20 Decimal and my R-75 is set as ID
5A Hex and 90 Decimal.

All of the Manual tuning works fine. I am currently developing the scanner
mode capability.
On the intro screen (R7100 in center) just close it with the "X" in the upper
right corner of the form. Be sure to select the baud rate and com port to match
your connection and use the save configuration in the configuration menu. Then
you can use the Auto-ID under the R7100 menu, it will show the detected decimal
ID and then you save the configuration once again. Now exit the program and restart
it. It will read in your configuration and use it.

You may then select Manual Tune to open the manual tuning area. All frequencies
entered are in pairs. In other words if you put in 162.55 or 99.50 or 76.25. You
cannot put in 162.5 or it will give an incorrect tuning.

Take care & Enjoy!

Wayne.

Download the code complete with VB6 sources here. (50KB, ZIPped files).

9.3 PERL

Since some years I have switched to Linux and rarely write software for Windows anymore. My language of choice to start with under Linux is PERL. I really love it, it's a great language.

I have written two modules which facilitate the use of the CI-V commands: icomdefs.pm and icomcivio.pm. Further you have to use a module for controlling the serial port. I use Device::Serialport, see your local CPAN.

To be continued.

9.4 C, C++, Visual C

To be done. Any volunteers?