IC-900 Remote Controller Clone Project, Part II

Updated 07/22/2022

Back to the Projects page...

Back to Part I ... The Conclusion


Welcome to Part II…

Contrary to the tone at the conclusion of Part I, the software development has progressed without interruption and just a scant 4 weeks on, the progress is very good. All basic radio functions have been coded and pass basic tests. This means that the unit can function as a multiband radio with the main features implemented: Frequency adjustment, duplex selection, TX offset adjustment, CTCSS tone on/off and adjustment, Volume/Squelch adjustment, HILO power control, rev/check feature, TS (freq step select), and band selection. Progress has been rather quick owing to previous work (see this discussion about the begninnings of my earlier attempts at building a "radio" using ICOM UX modules). Still, progress has not been without issue or pain.

First, I still had some hardware gremlins to address. These didn't show up in the initial validation, but became obvious once I started exercising the user interface. Chief among these were the solder shorts: one on the ICOM key/display board, and one on the flex-cable connection to the new ARM board. Additionally, a failure of the SIN comm connection was a bit surprising. Here, the data separateor circuit was copied almost verbatim from the ICOM design, and it was tested good! However, when attempting to use the system I discovered that the data didn't look at all good on an o'scope (thus, no surprise that it wasn't working). There was a significant "RC" component to the waveform. I finally worked out that I needed a load resistor on the power/data signal to get the data separator to work correctly. Curious.

After considering the issue, I realized that I had tested the data separator in parallel with the working IC-900 controller that I had borrowed - making it apparent that the borrowed controller loaded the signal in such a way that my design did not. This load turned out to be in the form of the Iq(dc) of the LM7806 voltage ragulator used on the ICOM design. For my design, I used a switching regulator circuit which has a substantially lower Iq(dc). The "loaded" I(ac/dc) was in the form of a high-frequency pulse train used by the switching regulator. This conspired to remove the Iq(dc) of the original ICOM circuit. A typical value of Iq(dc) for the LM78xx series is about 5 mA and a 2.2K resistor accross the 11(ish) volts at the input to the data separator provided that load rather nicely.

Speaking of the switching regulator, another issue had me replacing a couple of those parts because I missed a subtlety of the IC's enable pin. Most regulators I encounter allow the enable to be tied to Vin to force the part to be on all the time. With the part I used in this design, there is a 6V max limit on the voltage applied to the enable. Adding a series current-limit resistor was an easy fix, but I was confounded: I had used this part in another project and connecting the enable directly to Vin hadn't expressed this issue. Delayed learning... Drat!

Lastly, I found that I needed to consider adding a coin-cell battery to maintain the ARM's HIB RAM. When I originally started down this design path, I was rather pleased with myself at planning to use the Tiva EEPROM to maintain the radio channel memories. A vast improvement over designs typical of this vintage as it elliminates the need for a battery and all the mainenance headaches that come with it over time. However, I forgot about the fact that not only are there channel memories, but the overall state of the radio needs to be remembered during power-off cycles. This includes many variables that are highly transient and would not typically be maintained in EEPROM because they can be changed often (such as settings for VFO, volume, squelch, etc...).

One option is to monitor the supply voltage and produce an interrupt when power is removed. The idea is that the hold-up capacitance on the regulators offers a measure of time delay between the loss of primary power, and the voltage decay at the output of the 3.3V LDO. If this delay is long enough, and repeatable, it could be used to store "transient" data to EEPROM. Otherwise, a battery will be needed. The solution will end up being very close to that, only different.


How NOT to write real-time code…

Up to this point, I had been focused on hardware and low-level software. Once the hardware was no longer an issue, I had to switch gears and start thinking more about the user interface (UI). I had a template to work from in the form of the original controller operation. However, I wasn't allowing that to limit what was possible. But in the begining, I was mostly concerned just with the basics. Things like, get the S-meter to work and display the frequency. Then, get the radio part to work at the indicated frequency. Receive first, then transmit, and on from there. The work I'd done on the low-level code paid off as I found almost no issues with the basic I/O between the UI and the radio. This allowed the focus to remain on the UI. Rather quickly, I had a basic T/R radio. Soon after, I could adjust the frequency and CTCSS tone. Before I knew it, I was left with only the memory function as the last major feature, plus some minor dangling threads.

But let's not skip too far ahead just yet. To start out, I had to come up with a UI that worked and wasn't too easy to break with software changes. Since I do bare-metal design, I have to take extra measures to ensure that there are no "burrs" in the code. These are "loops" where the code has to wait for something to happen, or a time delay to transpire, before continuing on. Such coding structures bog-down the code execution and can often hang the system because the thing that is being waited on needs processing to happen. The result is that one must "un-roll" the logic and slice it into "events" that can be trapped and acted upon (quickly). A ho-hum RTOS (Real Time Operating System) does all that for you, but in the bare-metal world, you have to think in "slices" - all of the algorithms have to be broken into steps that can be executed in stages.

There are a couple of ways that I accomplish this. One is to use the concept of state machines, and another technique is to use "change" flags where execution is triggered when a data value changes. To make a simple state machine, a "state" variable is created and a corresponding state diagram is sketched out. For each "event", a state name is assigned (these become numbers in the code, but we can assign names to the numbers to help the code make sense). The state variable feeds into a switch() statement which directs the program flow to the identified "event". The code at that event runs in sequence with only the briefest of delays (ideally) before exiting the switch() statement. If conditions warrent, the "event" will modify the state variable to point to another event on the state diagram. In this way, one event can direct flow to another event to accomplish the task at hand. Along the way, various resources, such as GPIO or timers, are also employed to regulate the flow and direction of the code.

As long as the switch() statement is executed with some regularity, the state machine will progress through the state diagram over time and as external inputs dictate. Other switch() state machines using other state variables can then all be run in sequence to accomplish multiple tasks in what appears to be parallel. This is essentially what an RTOS does, but you don't have to think about it as much with the RTOS. For simple systems such as the one at hand, the thought overhead can be managed, which can reduce the system overhead required vs. that required to run an RTOS.

There are a lot of RTOS options out there. You can even write your own (I once wrote a simple one for the 6502, many moons ago). I'd argue that I essentially write an RTOS into my bare-metal designs. However, in doing so I have to re-invent the wheel for each new project. Most of my new designs are generally evolutions of something I've done in the past, so I end up re-using code often enough that the coding efficiency is not poor enough to scrap all of my past work and jump onto the RTOS train. While it generally works for me, I'm not advocating it for all. I think it is important to excercise bare-metal skills when one is developing for real-time microcontrollers, but on the whole, using some kind of COTS RTOS is going to be more efficient. Still, if you are game, check out the CCS6 project and source file github repo.


Five-pound bucket - 10 pounds of , er, "stuff"…

The primary issue with this application is actually one that an RTOS can't really help with on its own. The issue regards resource management. In this case, the LCD display. The problem revolves around the fact that there are a limited number of pixels in the viewing area and too many "items" that need to be displayed. As a result, some of the display fields have to be multiplexed from different data sources. For example, there are two bargraphs in the display area, one for main S/RF and one for sub S/RF. However, a bargraph would be helpful to display volume and squelch. To cover all these bases, we'd need 6 bargraphs in total. Since there are only two, we have to timeshare the bargraphs which means that there has to a method of selecting which resource is to be displayed. With only 7 bar-segments, I also wanted more resolution (the volume and squelch have a range of 0 - 34), so I decided to use the memory number digit to get a fine resolution of volume/squelch changes. By implication this means that the memory digit resource must also be multiplexed.

All in all, I'd end up spending most of my time managing the multiplex operations. With the myriad of features that I needed and wanted, this has become a bit of a challenge. Still, having been through this before, I at least knew what I was up against. The resulting code is reasonable, tho not perfect. Considering it from an outsider's perspective, the code would likely be onerous to modify because there are lots of opportunities for something changed at point "A" to break something over at point "W". It is my best effort, and I've generally done an OK job at traking down the breaks, but it still could stand with some improvement. Code comments are my only countermeasure at this point, so I am not likely to re-attempt the design unless some other influence intervenes.

To try and keep everything balanced, I organized the software into "processes" that are more-or-less focused on a particular resource or activity. These process functions are called in-order from an idle point in the command-line serial character polling function. As long as none of the processes enter any sort of long wait loop, the tasks all appear to essentially execute simultaneously. This is actually something that also must be kept in mind - the "appear" part in particular. All of the radio communications are handled in radio.c, and all of the User Interface (UI) operations are in lcd.c (the rest of the source files pertain mostly towards the interrupt and initialization resources). The UI is divided between inputs and outputs, with the inputs from the user and from the radio driving operations to the display and the radio. Many paths cross which is part of why there are many options for breaking things. However, there are no real options since the LCD space is fixed and finite.

Figure 1. LCD "pixel" map with pixel names.

The pixel map of Figure 1 illustrates what there is to work with. The LCD is depicted in the center, and the major areas are broken out around the periphery. Each break-out roughly corresponds to a "domain". The frequency displays (M00 - M6 and S00 - S6) are the main "action" point where numberic or symbolic strings can be displayed. The bargraphs are the primary graphical display for amplitude data. The memory# digit (M7/S7) is primarily for just that - the memory number. The "stock" and "new" indications are explained below:

Segment Name(s) Description/Where-used
M0-M6/S0-S6: Frequency display (main or sub). Also used by TX offset adjust, CTCSS tone adjust, SET mode, and memory label
MM0-MM6/SM0-SM6: SRF Meter (main or sub). Also used by VOL/SQU adjust (leftmost segment blinks)
MMEM/SMEM: Indicates memory #. "0-9", "A-Z" (with some characters excluded for clarity) {"0-9" on the IC-900}
MSKP/SSKP: Indicates skip of indicated memory # in scan mode
M00/S00: 500 Hz digit (not used by the IC-900). Blinks if CHECK/REV mode active
MTNE/STNE: On if CTCSS encode enabled
M/S Duplex (#27/47): TX offset triad. all off = Simplex, "DUP" = "+", "-DUP" = "-"
OW: Offset Write. Lit if offset adjust active
LOW: Indicates TX power setting for active band focus
VXO/RIT: Active if corresponding modes are active. Only applies to the 1200 MHz module
TS: Indicates "B" frequency step (otherwise, "A" step) for frequency adjustments. Blinks if edit step-size is active
MHz: Active if MHz mode active (thumbweel mode is indicated by a blinking digit in the frequency display)
PROG: Not used - likely use would be to indicate remote computer connection {indicates program scan mode on the IC-900}
BAND: No use planned {indicates band select mode on the IC-900}
SUB: Indicates that the UI focus is set to the sub-band. Otherwise, the focus is the main band
LOCK: Set by the position of the "LOCK" slide switch. Locks the UI buttons
TSQ/DSQ: Indicates CTCSS or DPL squelch modes active (requires custom decode module)
OP1/OP2: No use planned

To accomplish the multi-use aspects of the display features, a mode and timer scheme was employed to regulate those sections of the display that have multiple functions (these flags/timers also control the application of keystrokes). Part of the execution loop polls the timer for the active mode and clears the mode when the timer expires. Each coincident feature needs its own flag and timer (not all have a timer, such as the tone adjust mode).

In addition to the mechanics of the sharing interfaces, there needs to be a signal to the operator as to what is going on. For example, one thing I didn't like about the IC-900 was how the squelch and volume adjustments were accomplished. The SRF meter is deployed, but only if you hold down the corresponding button. Also, there are only 7 segments in the bargraph, but 34 steps in each adjustment. I wanted some acknowledgement of each change, so I decided to use the memory number digit. The numbers "0" - "4" would be a good choice, but then how would one distinguish these memory numbers from a volume or squelch adjustment? I decided to implement a unique segment pattern for the lower "digit" of the adjustment, as shown in Figure 2. None of the patterns shown are valid memory numbers so one can easily distinguish that there is an adjustment in progress (squelch is displayed in the main-band area, and volume is displayed in the sub-band area).

Figure 2. VOL/SQU adjust low-order symbol progression (the MEM# digit).


The KEY Point…

Figure 3 illustrates the IC-900 control unit. Of particular interest here are the keypress locations (buttons). These are the primary user input media for the system. The list below describes the button uses. Some of the button meanings have been usurped here to improve the operation of the radio. For example, on the IC-900, the band switch is accomplsihed by entering the SET mode, then moving to the BAND sub-menu, then selecting the band. Compared with the IC-901 (which has a dedicated BAND button), this is a lot of effort in order to switch bands. Since there is an MR and a VFO button, I decided to use MR to toggle between VFO and memory modes, and use the VFO button as the "BAND" button.

Figure 3. IC-900 Control Unit.

Button Name Focus Description/Where-used
POWER: --- Turns the control unit (and radio) on or off
VFO: m/s Selects next available band unit (no effect if less than 3 units are installed)
MR: m/s Toggles between memory and VFO mode. The "M" at the MMEM/SMEM location indicates that the memory mode is active
SUB: --- Toggles focus between MAIN and SUB bands. The focus (m/s) determines which band is modified by user input
M/S: --- Swap main and sub bands (no effect if less than 2 bands are installed)
HI/LO: m/s Toggles RF power level between High and Low (the actual power levels vary by module)
CALL: m/s A special, 1-key memory mode
MW: m/s Stores the current VFO parameters to the currently selected memory location
TS: m/s Tuning Step, Toggles between the "A" step size and the "B" steps size
T/DS: m/s Toggles CTCSS/DPL squelch modes
DUP: m/s Duplex, cycles between S, +, and - TX offsets
DUP (hold): m/s Activates offset adjust mode. Frequency display shows offset frequency and adjustment is using the thumbwheel mode
TONE: m/s Toggles CTCSS encode on/off
TONE (hold): m/s Enters CTCSS adjust mode. Frequency display shows CTCSS frequency
CHECK: m/s Enters or exits "reverse" mode. If duplex = "S", there is no effect unless the key reaches "hold" status. Any other key after entering "reverse" mode will cause the mode to cancel
CHECK (hold): m/s Enters "check" mode. Regardless of duplex, the frequency is adjusted to the TX frequency and the squelch is opened. Releasing the CHECK button exits the mode and returns operation to normal
MHz: m/s Toggles the MHz mode. When the MHz icon is on, dial or microphone up/dn will adjust the MHz digit of the frequency. In the thumbwheel mode, advances the active digit one to the right (or back to the left most digit)
MHz (hold): m/s Enters thumbwheel mode. Any other button ends thumbwheel mode
SET: m/s Enters the configuration loop
SMUTE: --- Toggles the sub-band mute status (unmutes the main band if the sub-band was muted)
SMUTE (hold): --- Mutes main band (or no operation if sub-band is unmuted)
SQL down: m/s Adjusts the squelch down one step
SQL up: m/s Adjusts the squelch up one step
SQL up (hold): m/s Sets the squelch to maximum (closed)
VOL down: m/s Adjusts the volume down one step
VOL dn (hold): m/s Sets the volume to zero Note: this is not a mute function
VOL up: m/s Adjusts the volume up one step

The "Focus" column indicates what a key's focus is (main or sub, or global). "(hold)" means that the button is pressed and held for at least 1 second. At this point, a double-beep is emitted from the beeper and a key event is generated with a "hold" attribute to differentiate it from the initial keypress. There is also a "release" attribute, but this is only used for the CHECK button. Note: for any case where "any key to cancel" is invoked, the key functions are suspended until after the key-press which performs the cancel operation.

The same flags used to direct display updates are also employed to steer the inputs. In some modes, certain keys change function, or serve as "cancel" keys. The dial and microphone up/dn inputs also must be re-directed according to what parameter is the current focus of control. Because of the separation of "input" and "output", the mode flags must be handled in separate locations. In this way, the "loop is closed" which is to say that the cause and effect are appropriately linked via the software.

Note that there are actually two input sources (the user and the radio), and two output sources (the display/user and the radio) even though there are just two distinct entities (the user and the radio). Perhaps this seems obvious, but it must be kept in mind when fashioning the UI because input from the user must generally be fed back to the user as well as sent to the radio. All these interconnections change when there is a mode change, so care must be taken when handing data that needs to be transferred between software modules. Because this system makes extensive use of buffers, it is generally not an issue for a module to stop taking data from a particular resource when a particular mode is in effect. Once the mode returns to "normal", the data flow resumes and no loss of continuity occurs.

Sometimes this comes in the guise of buffering change flags rather than data. While a data source is "muted", several changes may occur. The current data may be have been replaced several times, and may even have returned to its original state. However, the changes are buffered which means that the system will still react to the data and update the particular resource. This saves a lot of buffer memory as the change flags generally only need one word as they can be "OR'ed" with new changes which still "remembers" old changes.


The Same, only more better…

Overall, the operation of the controller is similar to that of the ICOM version. However, there are a couple of innovations that are worthy of note. The first is rather ho-hum, but useful to a degree. I re-implemented (and modified) my 7-segment alphanumeric character set (first developed for my GPS Wall Clock -- and no, I'm not the first to attempt this) to allow for more informative error messages as well as for text memory names. There are only 6 characters available in the frequency display space (I don't plan to implement a "sliding" message scheme which would allow for more characters) but this will accomodate any known call-sign, so it can at least be used to accociate the repeater call with a particular memory. In addition, there are a few characters that are non-ideal, but are the best one can manage with only 7 segments. The image below illustrates the character map for the numbers and letters. Preference is given to lower case characters. Knowing this helps resolve a couple of conflicts for "u" (u) and "U" (V). The other characters of questionable merit are "K", "m", "w" and "x". See what you think:

Figure 4. 7-segment alpha-numeric character map ("font").

Figure 5. "KE0FF" in 7-segment font.

All of the characters are unique, if cryptic, which elliminates problems of context. It turns out that call signs have a limited set of syntax rules which end up causing problems if one tries to use any of the numerals as stand-ins for an alpha character (e.g., using "6" for upper case "G"). "W6GY" thus becomes "W66Y" in the display - which leads to the question: how does one differentiate "W6GY" from "WG6Y" in such a situation? Hence the importance of uniqueness.

While this "font" is certainly not the best way to accomplish a plain text display, after much consideration, trial, and error I think that it is as good as one can get considering that the only other option is no option at all. This, at least, offers some means of better managing the considerable number of memories that will be available. As an aside, I think it rather unfortunate that two of the four possible callsign prefix characters for the US are "w" and "k".

The second innovation is what I'm calling "thumbwheel mode", alluded to previously. One of the main drawbacks with the IC-900 (and IC-901) is the tuning. All of the tuning is accomplished using either a single dial, or the microphone up/down buttons. A MHz mode is offered which helps somewhat, but you can still be stuck tuning through up to 100 dial clicks (or up to 200 if you don't think ahead) to get to the desired frequency. While a direct entry method is planned using the bluetooth interface, I needed something in the meantime to cover the gap. The thumbwheel mode fills this gap and is really just a variation on the original MHz mode. In thumbwheel mode, the system starts at the highest moveable digit for a given band (e.g., 100 MHz for the 1200 MHz module, or 1 MHz for the 10m module). The dial adjusts that digit (which flashes to identify it) and the MHz button steps to the next digit to the right (or back to the left-most moveable digit if all the way to the right). You can move anywhere in the band in about 5 seconds without trying all that hard. WAY better than the old way. A small reward for being in control of the software.


Where to next…

Memory and SET mode features are next. I would like to get some operational testing performed first, however. The code development has been rather hot-n-heavy up to now, and there are a number of details that have been run roughshod over. I need to populate some additional modules (I currently only have two attached) and spend some time tuning around and talking to folks.

I also need to think about the tone and digital squelch modes. The digital squelch module (UT-28) that ICOM offered looks to be un-obtainium. In addition, it is proprietary, so it would only work with other ICOM radios that were likewise equipped. This simplifies things to a great extent as I can actually think about focusing on making my own decoder for this radio. There is a CML part (CMX138) that can do CTCSS and DPL encode/decode functions. With a bit of effort, one could make a custom board that could be used to accomplish both modes. In addition, the RDU code would have to interface with the new option board and also interact with it in operation to accomplish the decoding task.

A terminal interface also needs to be written. The framework is already in place, but the commands need to be filled in. Something that can operate over the wired comm port at first, then move to the Bluetooth port. For the wired port, a maintainence approach is envisioned. Memory upload/download and some debug commands. For the Bluetooth port, real-time control will also be desired. This will allow something like my HM-133 dtmf interface to control the radio with modern convieniences like direct frequency entry and other amenities.

Back to Part I ... The Conclusion

Back to the Projects page...




Here is a listing of the various project documents that are directly relevant to this project:

SiLabs/Tiva Programming guide

RDU Clone Schematic

RDU Clone git repo (contains hardware and software artifacts)