Monday, January 12, 2015

Makefiles Made Easy

The bane of computer programming is the Makefile. As soon as there are more than one source file and a library, a Makefile becomes essential and generating one can be painful.  The Makefile syntax reminds me of a sendmail configuration file, which looks like someone banged his head on a keyboard:

# This rule ensures that all local mail is delivered using the 
# smtp transport, everything else will go via the smart host. 
R$* < @ $* .$m. > $* $#smtp $@ $2.$m. $: $1 < @ $2.$m. > $3 dnl


Way back in the dark ages, before there was m4, I actually edited a few sendmail files by hand - that was how I earned my UNIX beard - but good grief...

Same as with sendmail, you should never edit a Makefile manually these days.  It is a total waste of time.  There now are nice and simple utilities to do it for you!


GCC -MM

If you have a very simple project, then gcc can generate the Makefile for you:
$ gcc -MM *.c > Makefile
$ make

Error: cc1plus

If you get the following or similar error regarding cc1plus, then the solution is not at all obvious:
gcc: error trying to exec 'cc1plus': execvp: No such file or directory

The problem is that while gcc is installed, g++ is missing:
# yum install gcc-c++

Also see the previous post for details on installing gcc.

CMake

If your project is more complex, then cmake can generate the Makefile for you, but you need to give it a few instructions in the file CMakeLists.txt.  First install cmake and related tools:
# yum install cmake*

Now create the file CMakeLists.txt and tell it which are the output, source and libraries:
project(FlashProject)
cmake_minimum_required(VERSION 2.8.12.2)
add_executable(flasher flasher.c)
find_library(FTDI ftdi)
target_link_libraries(flasher ${FTDI})

Note that gcc assumes that library file names start with lib and end with .so, therefore only specify the base ftdi or whatever, not libftdi.so.

The first time you run cmake, you need to specify the compiler:
$ CXX=gcc
$ export CXX

After that, run cmake to generate the makefile complete with all the obscure, head banging, gobbledygook lines:
$ cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/herman/sw/ftdi


Now you should have a very detailed almost 200 line Makefile and can compile your project with:
$ make

La voila!


Serial Port I/O

Until about 20 years ago, most desktop and portable computers had serial and parallel ports built in.  Those ports were great for controlling and testing home brew gadgets.  Modern machines are blessed/cursed with USB ports, which are just getting more and more complex.

The result is that if you want to do anything at all in your Underground Lab or Rooftop Radio Shack, you need an Arduino,  a Raspberry or a Beaglebone embedded computer.

However, every respectable engineer has two or three FTDI USB to RS232 serial adaptors lying in a drawer (to control his Raspberry or Beaglebone).  These adaptors are great, since they effectively insulate your target system from your expensive computer, therefore whatever goes wrong on the far end, is unlikely to fry your machine and they are delightfully hackable.

I prefer the dongles made by SerialComm.  They are cheap and both RS232 and RS422/485 are available.

You can also get ones from Sparkfun that are even more hackable, or you can crack the case and remove the line driver chip from a regular off the shelf one if you are desperate for TTL I/O lines.

The secret Open Sauce is the libftdi project, which is available for Linux and Mac systems. Documentation and examples are here.  The online files are the latest and greatest.  Therefore it may be best to work with the header file on your machine which is /usr/include/ftdi.h since your installed version of the library may be older and some functions may be deprecated or missing.

Simple Bitbanging with libftdi

Here is a tiny little C program for an older version of libftdi, that will toggle the Tx, DTR and CTS lines on a USB RS232 adaptor.  Note that you have to run it as root (or join some or other USB group), otherwise the USB device will not open.

/* File flasher.c */
/* libftdi API Example LED Flasher */

#include <stdio.h>
#include <ftdi.h>

/* RS232 DE9 pins */
#define DCD 0x40 /* 1 in */
#define RX  0x02 /* 2 in */
#define TX  0x01 /* 3 out */
#define DTR 0x10 /* 4 out */
#define DSR 0x20 /* 6 in */
#define RTS 0x04 /* 7 out */
#define CTS 0x08 /* 8 in */
#define RI  0x80 /* 9 in */
/* 5 GND */


int main()
{
    unsigned char data = 0;
    unsigned char pins = TX | DTR | RTS;
    struct ftdi_context context;

    /* Initialize libftdi */
    ftdi_init(&context);

    /* Open FTDI dongle using FT232RL vendor & product IDs */
    if(ftdi_usb_open(&context, 0x0403, 0x6001) < 0) 

    {
        puts("ERROR: ftdi_usb_open()");
        return 1;
    }

    /* Set bitbang mode on the RS232 output pins */
    ftdi_enable_bitbang(&context, pins);

    /* Forever */
    for(;;) 

    {
        data ^= pins;
        ftdi_write_data(&context, &data, 1);
        sleep(1);
    }
}


Improved Bitbanging with libftdi

Here is a more advanced program with proper error checking.  Note that you have to run it as root (or join some or other USB group), otherwise the USB device will not open.

/* File flasher.c */
/* libftdi.so API Example LED Flasher */
/* Copyright reserved Herman Oosthuysen, 2015 */
/* License: GPL version 2 or later */
/* Use at your own peril */

#include <stdio.h>
#include <stdlib.h>
#include <ftdi.h>

/* RS232 DE9 pins */
#define DCD 0x40 /* 1 in */
#define RX  0x02 /* 2 in */
#define TX  0x01 /* 3 out */
#define DTR 0x10 /* 4 out */
#define DSR 0x20 /* 6 in */
#define RTS 0x04 /* 7 out */
#define CTS 0x08 /* 8 in */
#define RI  0x80 /* 9 in */
/* 5 GND */


int main()
{
    int i;
    int ret;
    unsigned char data = 0;
    unsigned char outputs = TX | DTR | RTS;
    struct ftdi_context *ftdi;

    /* Initialize libftdi */
    printf("FTDI Initialize\n");
    ftdi = ftdi_new();
    if (ftdi == NULL)
    {
        fprintf(stderr, "ERROR: ftdi_new()\n");
        return EXIT_FAILURE;
    }

    ret = ftdi_init(ftdi);
    if (ret < 0)
    {
        fprintf(stderr, "ERROR: ftdi_init() = %d\n", ret);
        ftdi_free(ftdi);
        return EXIT_FAILURE;
    }

    /* Open FTDI dongle using FT232RL vendor & product IDs */
    /* Plug the device in and run 'dmesg' to see these codes */
    printf("FTDI USB Open\n");
    ret = ftdi_usb_open(ftdi, 0x0403, 0x6001);
    if (ret < 0)
    {
        fprintf(stderr, "ERROR: ftdi_usb_open() = %d\n", ret);
        ftdi_free(ftdi);
        return EXIT_FAILURE;
    }

    /* Set bitbang mode on the RS232 output pins */
    /* pins: Output = 1, Input = 0 */
    printf("FTDI set Bitbang Mode\n");
    ret = ftdi_set_bitmode(ftdi, outputs, BITMODE_BITBANG);
    if (ret < 0)
    {
        fprintf(stderr, "ERROR: ftdi_set_bitmode() = %d\n", ret);
        ftdi_free(ftdi);
        return EXIT_FAILURE;
    }

    /* Flash for a little while */

    /* Note: Use ftdi_read_pins() to read data directly */
    printf("FTDI Flashing...\n");
    for(i = 0; i < 10; i++)
    {
        printf("%d\r", i);
        data ^= outputs;
        ret = ftdi_write_data(ftdi, &data, sizeof(data));
        if (ret < 0)
        {
            ftdi_free(ftdi);
            fprintf(stderr, "ERROR: ftdi_write_data() = %d\n", ret);
            return EXIT_FAILURE;
        }
        usleep(500000);
    }

    /* Done */
    printf("\nDone\n");
    ret = ftdi_usb_close(ftdi);
    if (ret)
    {
        ftdi_free(ftdi);
        fprintf(stderr, "ERROR: ftdi_usb_close() = %d\n", ret);
        return EXIT_FAILURE;
    }

    ftdi_free(ftdi);
    return EXIT_SUCCESS;
}


CBUS Discretes

In addition to the 8 lines used for RS232, there are 4 more, which are used to control the LEDs and RS485 drivers.  These lines can be controlled in a similar fashion using BITMODE_CBUS.  It is not clear whether one can interleave the two bitbashing modes in order to control all 12 lines at the same time and whether the lines will glitch if one does. See this example.

I also read that one can use the CBUS bitbashing concurrently with the normal serial mode, to provide a UART plus 4 discretes, but I have not tried it.

Compile and Test

Assuming that your Linux machine is configured properly with GCC, compile it thus:
$ gcc -o flasher flasher.c -lftdi
$ chmod 754 flasher

Now stick a LED with a 1k resistor in series onto the RS232 adaptor Tx and Gnd pins to see how it works and run it:
$ ./flasher

Install and Configuration of libftdi

If you don't have a GCC and libftdi configured system yet, then assuming that you have Fedora Linux:
$ su -
# yum update

# yum install kernel-headers

# yum groupinstall "Development Tools" "Development Libraries"
# yum install libftdi libftdi-devel

Now you can control the world!

Cmake

Also see the next post on Makefiles

Serial Port Tips

www.aeronetworks.ca/2015/01/serial-port-io.html
www.aeronetworks.ca/2014/10/serial-ports-revisited.html
www.aeronetworks.ca/2014/01/crcs-and-serial-ports.html
www.aeronetworks.ca/2013/10/serial-port-tricks.html
www.aeronetworks.ca/2013/05/usb-serial-device-with-unknown-ids.html
www.aeronetworks.ca/2015/10/reading-and-parsing-data-from-serial.html
www.aeronetworks.ca/2013/05/compile-moxa-serial-widget-device.html

La voila,

Herman

Thursday, January 8, 2015

Ecrasez l'infâme

A moment of silence please, in a world gone mad.

$ make bzImage
$ echo Not war.

Tuesday, January 6, 2015

MGL V6R ATC VHF Radio Control Protocol in Bash

The MGL V6R and V10, ATC VHF radios have a binary protocol (described in the V10 manual), in addition to the ubiquitous Garmin SL40 protocol.

The Emergency/Setup channel is 121.5 MHz.  Don't use it for testing...

In this example, I used the Bash let operator to do the calculations and printf is treated as a built-in function - TIMTOWTDI.

Frequency

Here is a simple Bash script that computes the frequency message (It doesn't check for out of range values!):

#! /bin/bash
echo MGL V6R ATC VHF Radio Protocol
echo

STX="02"        # Message header
DLE="05"        # Message header
CC="00"            # Active Frequency message ID = 00H, Standby Frequency message ID = 01H
KHZ="000000"    # Frequency in Kilohertz, decimal string (6 digits)
KHZH="000000"    # Frequency in Kilohertz, hexadecimal string (3 bytes)       
KHZ0="00"        # Frequency in Kilohertz, B0, LSB first
KHZ1="00"        # Frequency in Kilohertz, B1
KHZ2="00"        # Frequency in Kilohertz, B2, MSB last
SUM="00"        # XOR checksum from CC to last data byte, then XOR with 55H to invert some bits

if [ -n "$1" ]; then
KHZ=$1
echo "Frequency = $KHZ kHz Decimal"
echo

echo "Set serial port USB0 to 9600N81"
stty -F /dev/ttyUSB0 raw
stty -F /dev/ttyUSB0 9600
echo

echo "Compute frequency in hexadecimal"
printf -v KHZH "%06x" "$KHZ"
echo "Frequency MSB first = $KHZH Hex"

KHZ0=$(echo "$KHZH" | cut -c 5-6)
KHZ1=$(echo "$KHZH" | cut -c 3-4)
KHZ2=$(echo "$KHZH" | cut -c 1-2)
echo "Frequency LSB first = $KHZ0$KHZ1$KHZ2 Hex"
echo

# Bash by default handles numbers as decimal ASCII strings.
# A number starting with a zero is considered to be octal.
# A number starting with 0x is considered to be hexadecimal.
# The final modulo 256 reduces the answer to one byte
# and the printf makes it hexadecimal.
echo "Compute byte wide XOR checksum"
let "SUM=0x$CC ^ 0x$KHZ0 ^ 0x$KHZ1 ^ 0x$KHZ2"
let "SUM ^= 0x55"
let "SUM %= 256"
printf -v SUM "%02x" "$SUM"
echo "SUM = $SUM"
echo

# The format string "\x" tells echo to output each variable as an
# 8 bit binary value to the serial port,
# and not as two ASCII characters.
echo "Frequency message = $STX $DLE $CC $KHZ0 $KHZ1 $KHZ2 $SUM"
echo -en "\x$STX\x$DLE\x$CC\x$KHZ0\x$KHZ1\x$KHZ2\x$SUM" > /dev/ttyUSB0
echo

echo "La Voila!"
exit 1

fi
echo Example Emergency Frequency: f 121500
exit 0


I fixed a formatting bug in the checksum routine - these should work OK now.

Volume

Here are two more scripts to change the volume:

#! /bin/bash
# V6R Volume Down

echo MGL V6R ATC VHF Radio Protocol
echo

STX="02"        # Message header
DLE="05"        # Message header
CC="03"         # Volume DOWN message ID = 03H
D0="00"         # Unused
SUM="00"        # XOR checksum from CC to last data byte, then XOR with 55H to invert some bits

echo "Set serial port USB0 to 9600N81"
stty -F /dev/ttyUSB0 raw
stty -F /dev/ttyUSB0 9600
echo


# Bash by default handles numbers as decimal ASCII strings.
# A number starting with a zero is considered to be octal.
# A number starting with 0x is considered to be hexadecimal.
# The final modulo 256 reduces the answer to one byte
# and the printf makes it hexadecimal.
echo "Compute byte wide XOR checksum"
let "SUM=0x$CC ^ 0x$D0"
let "SUM ^= 0x55"
let "SUM %= 256"
printf -v SUM "%02x" "$SUM"
echo "SUM = $SUM"
echo

# The format string "\x" tells echo to output each variable as an
# 8 bit binary value to the serial port,
# and not as two ASCII characters.
echo "Volume Up message = $STX $DLE $CC $D0 $SUM"
echo -en "\x$STX\x$DLE\x$CC\x$D0\x$SUM" > /dev/ttyUSB0
echo

echo "La Voila!"
exit 1


and the other way:

#! /bin/bash
# V6R Volume Up

echo MGL V6R ATC VHF Radio Protocol
echo

STX="02"        # Message header
DLE="05"        # Message header
CC="02"         # Volume UP message ID = 02H
D0="00"         # Unused
SUM="00"        # XOR checksum from CC to last data byte, then XOR with 55H to invert some bits

echo "Set serial port USB0 to 9600N81"
stty -F /dev/ttyUSB0 raw
stty -F /dev/ttyUSB0 9600
echo


# Bash by default handles numbers as decimal ASCII strings.
# A number starting with a zero is considered to be octal.
# A number starting with 0x is considered to be hexadecimal.
# The final modulo 256 reduces the answer to one byte
# and the printf makes it hexadecimal.
echo "Compute byte wide XOR checksum"
let "SUM=0x$CC ^ 0x$D0"
let "SUM ^= 0x55"
let "SUM %= 256"
printf -v SUM "%02x" "$SUM"
echo "SUM = $SUM"
echo

# The format string "\x" tells echo to output each variable as an
# 8 bit binary value to the serial port,
# and not as two ASCII characters.
echo "Volume Down message = $STX $DLE $CC $D0 $SUM"
echo -en "\x$STX\x$DLE\x$CC\x$D0\x$SUM" > /dev/ttyUSB0
echo

echo "La Voila!"
exit 1


By implementing this in a Bash script, any problems can be fixed easily and one will probably only need to rearrange some variables in the final echo statements to make it work.

Zenity Press To Talk

Here is a more advanced script that uses Zenity to pretty things up and key the radio PTT over the serial port.  Note that if the radio 'hits you with a flat ignore', then you need to upgrade the firmware, since there is an old version around that has a PTT protocol bug:

#! /bin/bash
echo VHF PTT Script
echo Depends on: zenity, stty

echo "Set serial port USB0 to 9600N81"
stty -F /dev/ttyUSB0 raw
stty -F /dev/ttyUSB0 9600
sleep 1

# Message Protocol
# Repeat PTT message every 100 ms, timeout after 500 ms
STX="02"        # Message header
DLE="05"        # Message header
CC="0B"         # PTT message ID = 0BH
PTT="01"        # OFF=00H, ON=01H, default to ON
SUM="00"        # XOR checksum from CC to last data byte, then XOR with 55H to invert some bits

# Bash by default handles numbers as decimal ASCII strings.
# A number starting with a zero is considered to be octal.
# A number starting with 0x is considered to be hexadecimal.
# The final modulo 256 reduces the answer to one byte
# and the printf makes it hexadecimal.
let "SUM=0x$CC ^ 0x$PTT"
let "SUM ^= 0x55"
let "SUM %= 256"
printf -v SUM "%02x" "$SUM"
echo "SUM = $SUM"
echo "PTT message = $STX $DLE $CC $PTT $SUM"

# See the Zenity progress manual for details:
# https://help.gnome.org/users/zenity/stable/progress.html.en
PTT=0
(
while true; do
  echo $PTT
  echo "# PTT ON..."
  sleep 0.1

  let "PTT=$PTT+10"
  if [ "$PTT" -eq "100" ]; then
    PTT=0
  fi

  # Repeatedly send PTT ON message
  # The format string "\x" tells echo to output each variable as an
  # 8 bit binary value to the serial port,
  # and not as two ASCII characters.
  echo -en "\x$STX\x$DLE\x$CC\x$PTT\x$SUM" > /dev/ttyUSB0

done
) |
zenity --progress \
--title="VHF Radio" \
--text="PTT ON" \
--percentage=0

# Wait a little bit to avoid clipping the end of the transmission
sleep 0.2
echo "PTT OFF"

PTT=0
let "SUM=0x$CC ^ 0x$PTT"
let "SUM ^= 0x55"
let "SUM %= 256"
printf -v SUM "%02x" "$SUM"
echo "SUM = $SUM"
echo "PTT message = $STX $DLE $CC $PTT $SUM"
echo -en "\x$STX\x$DLE\x$CC\x$PTT\x$SUM" > /dev/ttyUSB0

# Wait for the dust to settle
sleep 0.1

exit 0

Zenity Frequency

This is a prettied script to set the frequency using Zenity sliders:

#! /bin/bash
echo VHF Frequency Script
echo Version: 0.1, 16 Sep 2015
echo Depends on: zenity, stty

zenity --question \
--title="VHF Radio" \
--text="Set VHF Radio Frequency?" \
--ok-label="OK" \
--cancel-label="Cancel"

if [ $? -eq 1 ]; then
  echo Cancel
  exit
fi
echo OK


# VHF frequency range: 118.000 to 136.975 MHz

MM=$( zenity --scale \
--title "VHF Radio" \
--text "Frequency 1XX MHz" \
--min-value=18 \
--max-value=36 \
--value=10 )

KK=$( zenity --scale \
--title "VHF Radio" \
--text "Frequency XX0 kHz" \
--min-value=0 \
--max-value=99 \
--value=10 )

K=$( zenity --scale \
--title "VHF Radio" \
--text "Frequency 00X kHz" \
--min-value=0 \
--max-value=9 \
--value=0 )

# Raw unvalidated frequency
echo Unvalidated Frequency = "1""$MM""$KK""$K"

# Valid frequencies must be divisible by 25 kHz
MMM="1""$MM"
KKK="$KK""$K"
echo MHz=$MMM
echo kHz=$KKK
let "MOD=$KKK % 25"
echo MOD=$MOD
let "KKK=$KKK - $MOD"
echo "Validated Frequency = "$MMM"".""$KKK" MHz"


echo Start a progress dialogue
zenity --progress \
--width=350 \
--title="VHF Radio" \
--text="Set VHF Radio Frequency...   " \

--pulsate &
PID=$!


echo MGL V6R ATC VHF Radio Protocol
echo

STX="02"        # Message header
DLE="05"        # Message header
CC="00"         # Active Frequency message ID = 00H, Standby Frequency message ID = 01H
KHZ="000000"    # Frequency in Kilohertz, decimal string (6 digits)
KHZH="000000"   # Frequency in Kilohertz, hexadecimal string (3 bytes)      
KHZ0="00"       # Frequency in Kilohertz, B0, LSB first
KHZ1="00"       # Frequency in Kilohertz, B1
KHZ2="00"       # Frequency in Kilohertz, B2, MSB last
SUM="00"        # XOR checksum from CC to last data byte, then XOR with 55H to invert some bits

# Decimal frequency
KHZ="$MMM""$KKK"
echo "Frequency = $KHZ kHz Decimal"
echo

echo "Set serial port USB0 to 9600N81"
stty -F /dev/ttyUSB0 raw
stty -F /dev/ttyUSB0 9600

# Wait for the dust to settle
sleep 1

echo "Compute frequency in hexadecimal"
printf -v KHZH "%06x" "$KHZ"
echo "Frequency MSB first = $KHZH Hex"

KHZ0=$(echo "$KHZH" | cut -c 5-6)
KHZ1=$(echo "$KHZH" | cut -c 3-4)
KHZ2=$(echo "$KHZH" | cut -c 1-2)
echo "Frequency LSB first = $KHZ0$KHZ1$KHZ2 Hex"
echo

# Bash by default handles numbers as decimal ASCII strings.
# A number starting with a zero is considered to be octal.
# A number starting with 0x is considered to be hexadecimal.
# The final modulo 256 reduces the answer to one byte
# and the printf makes it hexadecimal.
let "SUM=0x$CC ^ 0x$KHZ0 ^ 0x$KHZ1 ^ 0x$KHZ2"
let "SUM ^= 0x55"
let "SUM %= 256"
printf -v SUM "%02x" "$SUM"
echo "SUM = $SUM"
echo

# The format string "\x" tells echo to output each variable as an
# 8 bit binary value to the serial port,
# and not as two ASCII characters.
echo "Frequency message = $STX $DLE $CC $KHZ0 $KHZ1 $KHZ2 $SUM"
echo -en "\x$STX\x$DLE\x$CC\x$KHZ0\x$KHZ1\x$KHZ2\x$SUM" > /dev/ttyUSB0

# Wait for the dust to settle
sleep 1

echo Kill the progress dialogue
kill $PID

zenity --info \
--title "VHF Radio" \
--text "Frequency = "$MMM"".""$KKK" MHz" \
-- no-wrap

echo Done!


Use at your own peril though...

Interactive Scripts with 'chat'

A typical exchange with a radio or other embedded device goes something like:
  • Send a command
  • Get a response
  • If the response was good, then do an action
  • If the response was bad, then quit
Automating that with echo, cat and if statements is difficult.  The chat program is part of the pppd package and is probably installed by default.  You could use chat to do scripts more effectively than with standard bash commands.

http://www.tldp.org/HOWTO/PPP-HOWTO/x1219.html

For example, receive ERROR, then abort, receive nothing then send at&v, receive OK then set register 9 to 2, or timeout after 3 seconds:
$ chat -v TIMEOUT 3 ABORT ERROR '' at&v OK ats9=2 </dev/ttyUSB0 >/dev/ttyUSB0 

With a few lines like the above in a script, you can do most anything with a simple radio modem device.

Serial Port Redirection

When playing with serial ports in scripts, you will find that each time you open and close the port, it takes time, so it can drop characters and the buffer contents become unknown also.

A simple trick to avoid opening and closing the port with each line of code, is to use the exec program to do permanent redirection of the stdio files.  After that, any program that reads and writes to stdio will talk to the serial port.  This is perhaps better explained with an example:

DEV="/dev/ttyUSB0"
exec 3<&1 >"$DEV" <"$DEV"

File handle 0 is stdin, filehandle 1 is stdout and 2 is stderror, so 3<&1 means redirect stdout to a new handle 3 and the >"$DEV" means simultaneously also redirect it to the USB serial port and lastly, <"$DEV" means redirect the USB serial port to stdin.

From then on, your chat script doesn't have to add <"$DEV" >"$DEV" to the end of every line and if you need to echo a message to stdout, you can use handle 3 also.

More explanations on exec redirection here: http://wiki.bash-hackers.org/howto/redirection_tutorial

Serial Port Tips

www.aeronetworks.ca/2015/01/serial-port-io.html
www.aeronetworks.ca/2014/10/serial-ports-revisited.html
www.aeronetworks.ca/2014/01/crcs-and-serial-ports.html
www.aeronetworks.ca/2013/10/serial-port-tricks.html
www.aeronetworks.ca/2013/05/usb-serial-device-with-unknown-ids.html
www.aeronetworks.ca/2015/10/reading-and-parsing-data-from-serial.html
www.aeronetworks.ca/2013/05/compile-moxa-serial-widget-device.html

 
La voila!

Herman

Monday, January 5, 2015

Garmin SL40 ATC VHF Radio Serial Protocol in Bash

The Garmin SL30 and SL40 protocols are used by most aircraft and marine VHF radios.  These radios are very simple AM devices and basically one only needs to set the frequency.

The Emergency/Setup channel is 121.5 MHz.  Don't use it for testing...

Here is a Bash script that will compute a SL40 frequency message complete with its arithmetic checksum, using the bc calculator:

#! /bin/bash
echo Garmin SL40 ATC Radio Protocol
echo

# Set Active Frequency Message
HDR='$PMRRC'    # Header
IDH='0'        # ID
IDL='0'       
MHZ='119'    # 118 to 136 MHz
KHZ='100'    # multiples of 25 kHz
MOD='N'        # N=normal

SUM=0    # Arithmetic Checksum
MD=0    # MHZ scaled to ASCII decimal value
MA=""    # MHZ in ASCII character
KD=0    # KHZ scaled to ASCII decimal value
KA=""    # KHZ in ASCII character
MODD="" # MOD in ASCII decimal value
IDHD="" # ID high ASCII decimal value
IDLD="" # ID low ASCII decimal value
SUMH="" # Checksum as two digit Hex


# Example: "Set ATC radio to 119.100 MHz Normal"
# '$PMRRC00G4N29\r\n'

if [ -n "$1" ]; then
MHZ=$(echo "$1" | cut -d "." -f 1)   
KHZ=$(echo "$1" | cut -d "." -f 2)
echo "Frequency=$MHZ.$KHZ"
echo

echo "Set serial port USB0 to 9600N81"
stty -F /dev/ttyUSB0 raw
stty -F /dev/ttyUSB0 9600
echo

echo Calculate frequency characters
MD=$(echo "$MHZ - 48" | bc)
MA=$(printf \\$(printf "%o" $MD))
KD=$(echo "$KHZ - 48" | bc)
KA=$(printf \\$(printf "%o" $KD))
echo m=$MA
echo k=$KA
echo

echo Calculate checksum
IDHD=$(printf "%d" "'$IDH")
IDLD=$(printf "%d" "'$IDL")
MODD=$(printf "%d" "'$MOD")
SUM=$(echo "$IDHD + $IDLD + $MD + $KD + $MODD" | bc)
SUM=$(echo "$SUM % 256" | bc)
SUMH=$(printf "%02x" "$SUM")
echo SUMH=$SUMH
echo

echo "Frequency Message = $HDR$IDH$IDL$MA$KA$MOD$SUMH"
echo "$HDR$IDH$IDL$MA$KA$MOD$SUMH" > /dev/ttyUSB0
echo

echo "La Voila!"
exit 1

fi
echo Example: atcfreq 123.450
exit 0


The above script will take 119.100 MHz and output the message '$PMRRC00G4N29' to the screen and to the serial port.


Why on earth do I use Bash for this?  Because I'm a masochist and love to fight the Bash automatic type conversions...  

All Linux/UNIX machines have Bash (and Windows has Cygwin), therefore this is an easy way to debug and test any and all black boxes in a lab setting.

Modify at your peril!