HamHead: Arduino Based Universal Remote Head

Discussion in 'Arduino Playground' started by NQ4T, Aug 22, 2021.

ad: L-HROutlet
ad: l-rl
ad: MessiPaoloni-1
ad: HRDLLC-2
ad: Left-3
ad: L-MFJ
ad: L-Geochron
ad: abrind-2
ad: Left-2
  1. NQ4T

    NQ4T XML Subscriber QRZ Page

    After trying to help VE3CGA with what amounted to mostly a mental exercise for me; it lit a fire under me to start working on a project I've all but abandoned. I've changed the name and got over a "hump" that set me back. Formally known as ArFO, I'm now calling it HamHead. It is going to be an Arduino based remote head for a rig that might not have one. I'm creating this thread as both something to post updates/milestones on development and take suggestions. Plus a way of thinking outloud since I'm not much of a programmer.

    The history of this goes back to 2019 in what I'll stop lying and say was the "Good Buddy Controller". For whatever reason, I got this crazy idea it might be nice to tune my IC-725 like a CB radio...for monitoring CB on a planned road trip. What transpired was a crash course in Python and the CI-V command-set; but within a couple of days I had a Raspberry Pi blasting out VFO commands based on an array of frequencies indexed to channel positions. The Python code kept track of what "channel" you were on, did the 40 to 1 rollover for up and down, and it actually worked rather well. Level conversion/adaption from the RPi's onboard serial ports to CI-V was handled by a single 7404 IC. I soon realized what I'd made was essentially an external memory unit; and that it wouldn't be difficult to expand this out to control a VFO directly. Then I could mount the radio wherever I wanted since I'd have remote control head for it.

    I then realized no one wanted to wait 20 seconds for the head to boot up and with some Arduino under my belt, that I didn't really need the full operating system.

    The Code That Hung Me Up

    One of the things that hung me up in writing this over to Arduino was dealing with the BCD conversion. In Python; I'm pretty sure I just cast the data as an entirely different data type without doing anything else; just tell it that 0x73h is literally 73. I could probably do this in Arduino, but it's usually undefined behavior and a bad idea. I found some code that did this for me, but I said I never wanted to use it until I understood what it was doing. Realizing I needed to tackle this self-imposed hurdle, I worked through the snippit of code I'd found, yanked out the basic functions, and started working it out "by hand".

    Code:
    ((receivedBytes[x] >> 4) *10) + (receivedBytes[x]&0xF)
    
    It looks confusing especially since you don't know what receivedBytes is in this context. In regards to the radio driver, it's basically a byte array filtered and stored from the serial buffer; the software looks for the start and end bytes of a CI-V command, and drops everything between them in a byte array. The loop this is from works backwards through the array so it reads the data in little-endian like iCom sends it. X is just the array index that's decremented each loop. So how does it work? Simple?

    We start by shifting all of the bits in the byte four places to the left. This effectively drops the four least-significant bits from our BCD digit, giving us the 10's place...which we multiply by 10.

    The last piece basically runs a bitwise AND on the byte. This works just like the usual logic you see in digital circuitry, 1 and 1 is 1, anything else is 0. We AND agains a value of 0xF, or 00001111. This basically filters our result to the last 4 bits of the byte, giving us the single digit position of our BCD byte.

    Code:
    ((01110011 >> 4 * 10) + (01110011&00001111)
    0111 * 10 = 70
    
    01110011 &
    00001111
    ---------------
    00000011 = 3
    
    Add them together...and you have now have a decimal representation of your BCD byte. Mash them in to an array then multiply by the appropriate power of 10.

    It sounds like a lot of work for reading the frequency data from an iCom. I have to agree. As much as I like aspects of CI-V; this is one I don't. However my research seems to tell me that the CI-IV protocol uses the exact same basic command set as CI-V; one uses a serial bus while another is parallel. I'm sure the way they do this was based around the limitations of the older systems.

    But you see...we also have to generate this BCD format from our internal VFO variable. Which means dividing the VFO by powers of 10 to break it in to bytes, and converting those to BCD dropping them in the correct little-endian order. This is also a rather simple piece of code.

    Code:
    (((dec / 10) << 4) + (dec % 10))
    
    This time we take our two digit decimal number, divide it by 10, then shift 4 bits to the right. The % operator does division but just returns the remainder. This didn't look like it would work so I did it on paper:


    Code:
    (73 / 10) << 4 + (73 % 10)
    (7) << 4 + (3)
    
    (000001111) << 4 + (00000011)
    
    So...basically...we're bit shifting the binary value of 7 over 4 spots to the first nibble in our byte, this gives us 0x70h and a value of 112. Now we simply add 3. We get:

    Code:
    01110011
    
    115 in binary, or 0x73 in hex.

    Now that the hard part is done, dealing with the BCD conversions, I can actually start developing the rest of the application. I started out by writing a very basic outline of the API. Ideally a lot of the code will be independent of each other so that way if someone wants to code a new radio driver, or display driver; then that's all they'll have to do and just make it respond to known internal calls.




    Code:
    
    Radio Driver Interface:
    
    *radio.getFreq - get frequency from radio
    *radio.getMode - get mode from radio
    radio.setFreq - set current VFO frequency
    radio.setmode - set radiomode
    radio.switchVFO - VFO Swap
    *radio.setvfoA - select VFO A
    *radio.setvfoB - select VFO B
    
    radio.setFreq(frequency)
    radio.setMode(mode)
    
    * - these commands will have to be patched in VE3BUX driver since they don't exist.
    *- radio.getFreqMode will have to be hacked around since FT857 uses one command but iCom doesn't
    [/SIZE]
    
    Display Interface:
    
    display.line1
    display.line2
    display.mode
    
    I'm basing this around two rows of seven segment digit displays, with another that may indicate mode....or it may be LEDs, so I'm not specifying a large number of extra features. Whatever you want to display in line 1, whatever in line 2, and a command to fire off what operating mode we're in. It's up to the display driver to handle this data.
    
    Physical Interface....interface:
    
    control.up = VFO knob "up"
    control.down = VFO knob "down"
    control.mode(mode) = Mode Buttons
    control.memvfo = memory/VFO mode switch
    
    A knob. Some buttons. Whatever handles the physical inputs will just make these calls to the main engine.
    
    Engine:
    
    arfo.VFOA(frequency) = new VFO A frequency
    arfo.VFOB(frequency) = new VFO B frequency
    arfo.opMode(mode) = current operation mode
    arfo.freqUp = increase current VFO by step
    arfo.freqDown = decrease VFO by step
    
    The engine for the most part really only needs to know if we change mode or spin the knob. The VFO commands are for things like the iCom driver if you change the frequency on the radio itself. It's also part of the VFO sync heartbeat.
    
    Examples:
    
    Spin Knob in VFO mode up:
    
    Interface driver detects encoder movement up, fires control.up
    ArFO Engine increases VFO frequency variable by set step
    ArFO calls radio.setFreq(07205000)
    Radio driver sends command for new VFO frequency.
    ArFO calls display.line1(07205000)
    Display routine formats frequency for display type
    ArFO waits for new input
    
    Initialization (for iCom)
    
    ArFO powers up.
    ArFO calls radio.getFreq, writes to temporary VFO value.
    ArFO sends radio.switchVFO & radio.getVFO - gets new VFO value
    ArFO repeats this writing to third VFO variable
    ArFO compares temp value to vfoA to determine which VFO was first.
    ArFO puts the radio back on the starting VFO.
    Probably does not know the difference between radio's A&B, so A & B indicator might not match radio physically. It'll just be two VFOs.
    
    Background sync
    
    ArFO, after idle a while, will send a radio.getFreq to ensure it's sync'd with radio.
    
    

    The whole thing of flipping between VFO mode and the internal memory mode will come later. But hopefully when this is done one will be able to build a remote head (or people can sell kits, I don't care. Entire project is being released under very permissive open-source.) that just plugs in to the CAT/CI-V port. Now you can mount any mobile rig anywhere you want and just run the remote head to your control area. Future enhancements might include features will probably include some features for controlling two radios for satellite work.

    That's all I've got right now.

    73,
    Jay/NQ4T


     
    Last edited: Aug 22, 2021
    AA5BK and W2SGM like this.
  2. NQ4T

    NQ4T XML Subscriber QRZ Page

    This weekend I managed to stumble across a couple of items, my CI-V interface and my Arduino Mega. I knew I had 'em, just had to find 'em. Before I start explaining what drove me to having a massive headache last night, I'll briefly talk about the CI-V interface.

    [​IMG]

    So I'll admit this is a bit fancier than it needs to be since my original goal for this breadboard was to be a keying/CI-V interface for my 7100 for bit-banging FSK in to the rig; so ignore the FTDI module and the transistors over on the right. The main event with this is the 7404. A couple years ago I found some schematics for a level converter for RPi's 3.3v serial pins; someone had adapted it in to a CI-V adapter. I found it worked rather well and better than the two-transistor circuit that's common. (The transistors on this breadboard are not an attempt at that and are basically on the DTR/RTS lines). It seems like it's basically using the inverters to both flip the logic and act as isolation; the same way the two transistor method works. I never actually tested it in this configuration...in fact I never ran anything through it until last night when I pulled the FTDI module off and wired one of the Mega's serial ports to it. But I wrote some quick code to make sure it was working as intended....and to my surprise it was able to handle 19200 baud CI-V just fine.

    I loaded my previous alpha code that basically pulled data from the serial port and converted it from BCD to integer and displayed it. So I fired it up and sure enough, if I spun the dial on the radio my ARduino's serial console was spitting out the VFO. Great. Now let's try implementing something new.

    One of the things I'd been thinking about was configuring the CI-V address. I could do it with dip-switches and have it loaded every time...but I was thinking of trying something else. CI-V has a "special" radio address of 0x00, which means "all rigs on the bus". So just address everything to 0x00 and be universal, right? Not quite. It turns out this only works if your rig has CI-V transcieve on. Now for my old IC-725 this isn't a problem, it doesn't have that. Newer rigs, however, do. Then I tested this on my 7100 it didn't work; CI-V transcode was off. However since I was just fooling around, I also had the idea that I could discover the CI-V address of the radio by sending a packet and then reading the radio's address from a response. This didn't work and just lead to a massive 9pm headache. I have no clue why. Debugging isn't easy unless you write a bunch of code to output debugging...you can't just load up a debugger and poke around the memory. I took some tylenol, called it an early night, and woke up much earlier than usual this morning as a result.

    With CI-V transcode turned on, I could now actually see what data I was sending the radio through the use of RealTerm attached to the USB Com ports. This didn't tell me much, so I added some code to display the array contents after they were added to the variable by the serial routine.

    Code:
    Array: 
    FE0880908244120
    VFO A: 124482kHz
    Array: 
    FE088008344120
    VFO A: 124483kHz
    Array: 
    FE0880108344120
    VFO A: 124483kHz
    
    The VFO information is in fact correct, but my Array looks wrong. What I should expect to see is something like this:

    Code:
    FEE088090824412
    
    The VFO frequency was correct, but my Array display was not. The leading F was dropped and there was an additional 0. I can account for the additional 0; I developed this 735 backwards compatibility mode. The leading FE and final FD are dropped from our data when written...so that's fine. But this...isn't right; and I'm not sure if it's just the way Arduino is handing the data. Either way...attempts to read the byte that contains the radio ID and write it to the programs memory failed. I would watch it send the initial command (in this case a VFO read), but then nothing after the reply. I wasn't even seeing the echo which I'm pretty sure was wrong. It may be disabled somewhere in my rig, though I thought CI-V transcieve would have enabled it full time.

    I gave up on that and decided to start working on some other areas. One of things I want to implement is a feature that if the thing doesn't see a response, it may attempt to turn the rig on; this is CI-V command 0x18 0x01. So using realterm attached to the USB side, I punched in the proper command. Not only did the radio flip on, but I saw this on my Arduino console.

    Code:
    Array: 
    FE88E0181
    VFO A: 1184210kHz
    
    Now this data array looks 100% correct. The VFO is wrong because it's not VFO data obviously....but it did confirm that leading 0s were being dropped from the display and that my function can at least pull all the right bytes.

    Ultimately I decided I needed to change a number of things because I either can't implement them or the Arduino just gets too confused. For starters, there would be no auto-address finding or use of 0x00; this would just cause too many hassles.

    Secondly...I decided I might have to deal with the echo response differently. My main idea was after grabbing the CI-V frame off the serial port you look at the appropiate byte; if it's addressed to the radio then we assume echo and drop it. But...alas, that didn't work. IT wound up breaking the function. So after some basic rethinking; I think what I will have to do is handle the echo with my write routines. I have to specify how many bytes from a byte array I want to send to the radio. So I figure I send a CI-V command, then immedately step through a loop that reads the same number of bytes out of the serial buffer, I should clear out any echo response. This may wind up being a better method anyway. I haven't decided to implement that yet.

    I ordered a couple of 8digit 7segment LED modules and a rotary encoder so I can start mocking up hardware. But I think I'm going to have to spend some time figuring out how I'm going to actually control and deal with the serial data rather than trying to figure out how to deal with the BCD conversions.

    It's a hiccup for sure. It means instead of seeing a prototype this week, it might be in a few weeks. I need to get communication with the radio really stable before I go elsewhere.
     
  3. NQ4T

    NQ4T XML Subscriber QRZ Page

    So having an itch to get some code working I decided to start "fresh" and try to just have the Arduino pass me the array of bytes over the serial port so I can see if my routines for reading/filtering data work.

    [​IMG]

    It turns out I wasn't thinking clearly when I looked at my non-formatted hex output. When the radio sends an update to it's frequency or mode to the bus, it addresses it to 0x00, not E0. So the FE088 from earlier was in fact correct. I rewrote the routine to strip out our control characters entirely. Whether or not this makes it more optimized, it's a lot easier for ME to understand. Here's my new routine:

    Code:
    void getdata() {
      byte rb;
      while (Serial1.available() > 0 && newdata == false) {
        rb = Serial1.read();
        if (rb == 0xFE) {
          newdata = false;
          i = 0;
        } else if (rb == 0xFD) {
          rxbytes[i] = '\0';
          newdata = true;
    //    i--;
        } else {
        rxbytes[i] = rb;
        i++;
      }
    }
    }
    
    Here's what it replaced:

    Code:
    void recvBytesWithStartEndMarkers() {
        static boolean recvInProgress = false;
        static byte ndx = 0;
        byte startMarker = 0xFE;
        byte endMarker = 0xFD;
        byte rb;
       
    
        while (Serial1.available() > 0 && newData == false) {
            rb = Serial1.read();
    
            if (recvInProgress == true) {
                if (rb != endMarker) {
                    receivedBytes[ndx] = rb;
                    ndx++;
                    if (ndx >= numBytes) {
                        ndx = numBytes - 1;
                    }
                }
                else {
                    receivedBytes[ndx] = '\0'; // terminate the string
                    recvInProgress = false;
                    numReceived = ndx;  // save the number for use when printing
                    ndx = 0;
                    newData = true;
                }
            }
    
            else if (rb == startMarker) {
                recvInProgress = true;
            }
        }
    }
    
    Okay then...now I'll write something to look at bytes in the array and decide what to do.
     
    N3TGY likes this.
  4. NQ4T

    NQ4T XML Subscriber QRZ Page

    Well, I've spent most of the day tinkering...but at one point the LED modules and rotary encoders I ordered showed up. So I decided to work on getting those going. About an hour of tinkering I had this:



    That is a MAX7219 based LCD displaying the current frequency of the rig...after converting it from 4 bytes of BCD information. I have not yet figured out how (or if) I can add another decimal point or drop the trailing 0. The libary for the display isn't exactly the easiest to work with...although it's easier than some (but maybe not as easy as others). That's something I can play with after I get this thing out of hacking code and organizing it a bit better. But like always...you hit one "milestone" in a project and you want to get to the next. In this case...I really wanted to get the absolute basic functionality implemented. Spin knob on Arduino, update VFO on radio. Well...several hours of test code for debugging and one headache later....here it is:



    So....I now have at least proven to myself it's more than just theoretically possible.

    I'll get around to making a full repository this weekend; I tend to save my coding for non-workdays. So until sometime on Friday....I'll leave you with a copy of the code used in the second video.

    Code:
    #include <LEDMatrixDriver.hpp>
    #include <Rotary.h>
    // Rotary.h uses hardware SPI on Mega2560 pins 52 and 50 or something
    const uint8_t LEDMATRIX_CS_PIN = 49; // You're not going to have GPIO49 on an Uno.
    const int NO_OF_DRIVERS = 1; // Each MAX7219 driver can drive eight 7-segment displays.
    LEDMatrixDriver lmd(NO_OF_DRIVERS, LEDMATRIX_CS_PIN);
    const byte numBytes = 32;
    byte rxbytes[numBytes]; // store CI-V incoming bytes here
    long dec[5]; // part of the decimal to vfo conversion
    int bc = 0; // byte counter
    boolean newdata = false;
    boolean newvfo = false;
    unsigned long vfoa; 
    byte digit; // used by the display routine.
    
    Rotary rotary = Rotary(2, 3);
    void setup() {
    Serial1.begin(19200); // I'm using a Mega2560 so I have multiple hardware UART
    // hardware interrupt for rotary encoder
    attachInterrupt(0, rotate, CHANGE);
    attachInterrupt(1, rotate, CHANGE);
    // copied from display library initialization
      lmd.setEnabled(true);
      lmd.setIntensity(2);  // 0 = min, 15 = max
      lmd.setScanLimit(7);  // 0-7: Show 1-8 digits. Beware of currenct restrictions for 1-3 digits! See datasheet.
      lmd.setDecode(0xFF);  // Enable "BCD Type B" decoding for all digits.
    }
    
    void loop() {
    getdata(); // check for serial data
    if (rxbytes[0] == 0x88) newdata=false; // ignore echo (for now. I have an idea)
    if (newdata == true) gogovfo(); // Go go VFO!
    if (newvfo == true) disp(); // update the LED
    
    }
    void getdata() {
      byte rb;
      while (Serial1.available() > 0 && newdata == false) {
        rb = Serial1.read();
        if (rb == 0xFE) {  // if it's a start byte we don't need it. 
          newdata = false; // make sure we keep looping
          bc = 0; // i don't trust myself
        } else if (rb == 0xFD) { // end of the frame
          rxbytes[bc] = '\0'; // terminate the string
          newdata = true; // indicate there's new data
          //i--; // I thought I needed this
        } else {
        rxbytes[bc] = rb; // write the byte to the array
        bc++; // increment byte counter
      }
    }
    }
    /**
    "Go Go VFO" is requires some room to explain just what it does. The data sent
    in is both BCD and little-endian; so I not only have to convert the data from BCD,
    but I have to load the bytes "backwards". 
    
    Grab a byte, bitshift it 4 to the left, multiply it by 10. Take that same byte, bitwise
    AND it aginst 00001111, add them together. Now add those together in to
    a new array; multiply each entry in the array by the appropriate power of 10, 
    add all that together in to a new variable. 
    
    Oh, and then set newvfo to true so we can update displays.
    **/
    
    void gogovfo() {
        int i = 0;
      long bcd[4];
      bc -= 2; // adjust for array index starting at 0 and ditch the V/U byte
      if (rxbytes[2] == 0){
        for (int x = bc; x > 2; x--) {
          bcd[i] = (((rxbytes[x] >> 4) *10) + (rxbytes[x]&0xF));
          i++;
        }
        vfoa = ((bcd[0]*1000000)+(bcd[1]*10000)+(bcd[2]*100)+(bcd[3]));
        newvfo = true;
        }}
       
    void vfoup() { // this is called by the ISR for the rotary encoder.
      vfoa += 1000; // right now we're just adding 1000hz, but this will be selectable
      vfotobcd(); // black magic voodoo
      newvfo = true; // since we technically updated the VFO
    }
    
    void vfodown() { // this is the same thing, but the other direction
      vfoa -= 1000; // drop 1000hz
      vfotobcd(); // do that voodoo that you do so well
      newvfo = true; // since, you know...new VFO
    }
    
    /**
    Here's some black magic for ya. In order to go from the internal VFO
    number, I have to basically reverse the process for getting it form the
    radio; convert everything in to BCD digits in little-endian. 
    
    Do a bunch of math on vfoa that has the basic effect of splitting it in
    to two digit chunks, writing each to an array.
    
    Now divide each array entry by 10, bitshift right, add that by the remainder
    and slap them in a byte. Do that 4 times running backwards through the 
    array.
    
    **/
    void vfotobcd() {
    byte bcd2[10];
    int dec[5];
    int i = 3;
     dec[0] = vfoa / 1000000;
      dec[1] = (vfoa % 1000000) / 10000;
      dec[2] = (vfoa % 10000) / 100;
      dec[3] = vfoa % 100;
    for (int x = 0; x < 4; x++){
      bcd2[i] = (((dec[x] / 10) << 4) + (dec[x] % 10));
      i--;
    }
    
    // There's got to be a better/cleaner way of doing this I haven't figured out yet
    Serial1.write(0xFE); // start byte
    Serial1.write(0xFE); // start byte
    Serial1.write(0x88); // IC-7100 hex address
    Serial1.write(0xE0); // standard E0 controller address
    Serial1.write(0x00); // no-reply VFO change
    Serial1.write(bcd2, 4); // the BCD data
    Serial1.write(0xFD); // end byte
    }
    
    void disp() {
      unsigned long vfod = vfoa; // we have to copy this or we'll break vfoa's entry
      //bc++; // I thought I needed this.
    
    /**
    I know it's a for loop and it looks like it's just getting the remainder
    and dividing by 10 each time to split out the digits, but I haven't
    actually figured out how this works. Taken from the library example.
    **/
        for (digit = 0; digit < 8; digit++) {
          lmd.setDigit(digit, vfod % 10, digit == 3);
          vfod /= 10;
        }
        lmd.display();
        delay(10);
    // set these false or you're going to have problems
        newdata = false;
        newvfo = false;
    }
    
    // this came right from the rotary library example.
    void rotate() {
      unsigned char result = rotary.process();
      if (result == DIR_CW) {
        vfoup();
      } else if (result == DIR_CCW) {
        vfodown();
      }
    }
    
    ....phew.

    73.
     
    N3TGY and AA5BK like this.
  5. NQ4T

    NQ4T XML Subscriber QRZ Page

    In keeping with this being a "weekend project", I sat back down with it yesterday, played with the displays a bit, and realized I as going to have to build a better power supply. So while I'm waiting for those parts to show up, I figured I could always tackle the core of communicating with the radio. The last thing I started working on last year was the idea of an initialization routine. The head could be programmed to remember what the last operating state of everything was; and with something like FRAM you could literally just store all your radio variables there all the time. But I wanted to build as dumb of a device as possible. This thing needs to learn what the radio's state is when it boots up. It's pretty simple to just ask the radio a bunch of questions. And in fact...many many hours later I can show you this fine example:

    Code:
    Initalizing VFO Variables.
    Getting Starting VFO...
    Sending VFO Read
    RX Bytes: 88 E0 3 E0 88 3 20 29 9 7 0
    Sending VFO Swap
    RX Bytes: 88 E0 7 B0 E0 88 FB
    Sending VFO Read
    RX Bytes: 88 E0 3 E0 88 3 0 50 36 28 0
    VFO A: 7092920
    VFO B: 28365000
    Reverting to initial VFO.
    Sending VFO Swap
    RX Bytes: 88 E0 7 B0 E0 88 FB
    Done.
    
    This pretty much looks great, right? It does...and it accomplishes a task. It writes the VFO radio starts on as vfoa, swaps vfos, writes the second as b, then flips back. The thing you don't account for doing this is whether the radio started on A or B. In this example, A and B are swapped from my radio's physical A/B. Does this really matter? I suppose in a situation where you're relying on this for say, mobile operation; it probably doesn't. Maybe for split; but I think even then the radio just flips to the other VFO regardless of whether it's A or B. This is probably why last year when I attempted to do this before putting the project on hold. I had a lot of issues working with the serial port that I attributed to just needing more experience working with serial data. So I picked back up with my original idea for the "long initialization"; read the VFO we start on, switch to vfo A, read it, switch to vfo b, read it, compare our starting vfo, flip back to where we started. So after a few hours of poking around, I was able to achieve this:

    Code:
    Initalizing VFO Variables.
    
    Getting Starting VFO...
    Sending VFO Read
    RX Bytes: 88 E0 3
    RX Bytes: E0 88 3 0 50 36 28 0
    Setting VFO A
    RX Bytes: 88 E0 7 0
    RX Bytes: E0 88 FB
    Sending VFO Read
    RX Bytes: 88 E0 3
    RX Bytes: E0 88 3 0 50 36 28 0
    VFO A: 28365000
    Setting VFO B
    RX Bytes: 88 E0 7 1
    RX Bytes: E0 88 FB
    Sending VFO Read
    RX Bytes: 88 E0 3
    RX Bytes: E0 88 3 10 28 9 7 0
    VFO B: 7092810
    Reverting to initial VFO.
    Started on A
    Setting VFO A
    RX Bytes: 88 E0 7 0
    RX Bytes: E0 88 FB
    Done.
    Initalizing VFO Variables.
    
    In addition to being the long initialization, it's also the long debug output. I was having all kinds of problems getting this to work in the past and my first "quick try" worked about as well; so I started coding debug output. I thought it was timing...but turns out I just really goofed dealing with the echo and response. With that working I added checking for echo and response in the routines that read the data out of the serial buffer and in to the array. As you can see from RX Bytes (which just spits out the hex values of the array), our routine is stripping out the CI-V control characters (0xFE and 0xFD). Echos can be filtered out by looking at the first byte, responses by the third byte. By nesting an if statement inside an if else in a while loop, calling it once will dump the echo and ack. I still haven't handled the possible situation where there's another incoming packet. Right now I make the assumption that when I send the VFO B command, there will only be the echo and ack packet in the serial buffer. If there was a third packet of data to get in the buffer after those two, I currently don't have a way to handle it. That's something I'm working on. Right now I just call the getdata once after sending a command and manually call the routine to process the data if I expect some to be there. Of course..it's almost trivial to make this even fancier:

    Code:
    Initalizing VFO Variables.
    VFO A: 28365020 - D-Star DV
    VFO B: 7092920 - LSB
    Started on A
    
    Now it grabs the operating mode and determines what it is. Right now it's a large chunk of code since I'm dealing with strings for text output. But it did give me some ideas as to how I might handle something to automatically figure out what to do with a CI-V packet.

    I also have a Gitlab repository for this: https://gitlab.com/dewdude/hamhead. All the code I'm currently working on is in the alpha folder.
     
    AA5BK likes this.
  6. NQ4T

    NQ4T XML Subscriber QRZ Page

    Two Dots, Trailing Zeros, and Transcieve Data

    If you watched the videos I posted earlier, you saw the demo code where I was actually controlling the VFO from the prototype and updating it with an LED display module. There were are a number of things that bugged me about these LED displays:
    • only a single decimal point
    • leading 0 below 10MHz
    • lousy display driver
    The example code given with the driver for controlling seven segment displays was about as in-depth as this driver got; the Max7219 is more used for dot matrix LED stuff. Could it even support two decimal digits? I mean the hardware can, sure. But the driver did not. I poked around the drivers source code and after making my own attempted modifications, was able to figure out just what it was doing. More importantly, I figured out how to make it let me specify two decimal places, as well as add some stuff to blank out a leading 0.

    In fact, after I got that working; I then implemented VFO switching using the push button. Despite the fact I haven't gotten the power supply built to do two displays...someone may just want one. So I went along happily watching the radio flip VFOs and LED change every time I clicked it. Everything I'd implemented was working the way I wanted it to; at least until I started doing transcieve data.

    When you have a rig with transcieve mode on, it changes the behavior two ways; it will now respond to a special 0x00 address and it will send frequency and mode changes. The first video I posted of just getting frequency data was working with the transcieve data. This is all fine and dandy until you change the VFO on the radio.

    This is where I want to throw out the idea of using transcieve data at all.

    When you change VFOs on the radio it sends 19 bytes at once; a mode change and frequency change. This can be a problem since all the software sees is a mode and frequency change. This means I need to do *something* to try and interpret a 19 byte block of mode followed by frequency as a VFO swap. We can gladly send a VFO swap command to the radio (in addition to specifying which VFO we want); but it doesn't indicate this has happened in transcieve.

    Getting transcieve data to work outside of using just transcieve data has proven to be difficult. It's largely just down to how I'm doing things and how hacky some of my code is. I have some idea of what's going on; non transcieve data is making it in to the transcieve loop when it should have been cleared by previous functions. I think this is all down to timing and as much as I would love to have this thing respond just like it was native hardware, I may have to actually slow it down artificially to give time for data to properly down the bus. I have noticed my LED display updates before my actual 7100 head starts to respond. Even after I've told the code to wait until the serial is done sending before exiting.

    But it's still progress. Last year I couldn't get data input to work at all.
     
    WA4SIX likes this.
  7. WA4SIX

    WA4SIX Ham Member QRZ Page

    I only hack & butcher other people's code. Did an arduino band controller for auto bandswitching for my Metron MA1000 & IC-7000 that works crudely.
    I will follow your project eagerly.

    Ed
     
    NQ4T likes this.
  8. NQ4T

    NQ4T XML Subscriber QRZ Page

    So what if instead of having some physical hardware for level conversion we instead just bit-banged that stuff directly out of the Arduino? No max232 based interface, no 7404 based interface, no two transistor job. Just straight from the CI-V remote port directly in to the Arduino.

    That is what I got derailed with today.

    When using an old-school or "fully compliant" RS232 port, we need level conversion go to between whatever voltages the port is using and 5V TTL. The secondary, but equally as important reason, is because CI-V is an open-collector half duplex bus. The 7404 interface I use primarily uses two inverters each on the TXD and RXD lines coming from the Arduino to accomplish this; in fact I originally got it's main operation from a version that was designed to level-convert for an RPi. By pulling the inverters high using different voltages you could achieve the isolation, logic flipping, and level conversion you wanted. The two-transistor circuit I believe worked the same way...I never fully studied it. Primarily what I've been using a physical CI-V adapter for is to handle something that's already TTL UART down to half-duplex open collector.

    Over the last week I've been giving consideration as to what I ultimately want to do about the transcieve data problems I'd been having. The easiest solution is to just forget about using it, at least for now; and pretend the only control of the radio that matters is from the head. My thinking is that in a mobile installation where you'd use this; you're doing it because you can't actually get to the radio, so keeping track of changes on the radio shouldn't need to be done. If one was to decide to treat it as a wired remote in the shack...then maybe processing transcieve data would become more of an issue. But I thought about breaking out one more trick...as it was on the very long-term goals list. Half Duplex Software Serial.

    The ATmega328's only have one hardware UART port, which explains why I'm developing on a Mega2560; I wanted to be able to have a hardware UART port for the radio and still use the "built-in" one for getting debugging prompts. While I had intended to use hardware UART for the radio from the start; I do recall wondering if it might be possible to "bit-bang" CIV directly out of the thing. This would mean you'd need an absolute minimum amount of hardware to connect the radio to the Arduino; but at the expense of using CPU cycles to manually pump the data rather than dedicated hardware. My recent attempts showed me at, at least at 16MHz, the Arduino was running a heck of a lot faster than I actually needed it to; so I could probably waste some by using software serial stuff...if it worked. It might provide enough "slow down" to make transcieve data easier to work with....or just help eliminate the issues by the nature of the thing being half-duplex. I loaded up a modified library to do half-duplex on a single pin a bit better and started tossing together some code; a simple serial port mirror. Take take from one serial device, transfer it to the other.

    Initial tests went....okay. I didn't seem to be getting anything out of the radio and I couldn't send anything from the serial-port attached to the mega's USB; but my test command that sent a VFO change inside the code worked. So for a while I attacked this problem as something in the driver; maybe it wasn't flipping the port back properly, maybe I needed to specify pullups, maybe it was a logic-inversion problem. I really didn't get anywhere until I realized half my problem was RealTerm not wanting to actually send the bytes in the first place. I fixed that...and turns out I was able to send bytes just fine; I just couldn't get any of them back. I tried a couple of different ports, but it wasn't until I tried the one with the internal LED attached (13) that it worked. I originally picked that in the thought I'd see the thing blink and could use that as additional debug. I started wondering if it had anything to do with the resistor and LED going to ground permanently attached to that pin; but that turned out to not be the case. For reasons I haven't looked in to yet, at least pins 4 - 7 on my mega2560 won't work; but at least 10 - 13 do.

    Even if this does nothing to get transcieve data working the way I want right now; the interfacing for CI-V just got cheaper and can't get much simpler.

    It can be an okay way to learn. So much of my stuff was based on staring at example code, poking at it, figuring out how other people's code worked, seeing if I could rewrite it or add whatever functionality I needed. I do in fact shoot for the absolute crude at times. Right now I'm mostly doing some kind of development over my weekends, so that's usually the best time to check in. I'll probably post another update before Monday evening. I've been studying how Arduino drivers are written and I'm thinking I might start developing a legit driver for CI-V. I'll probably play with this method a bit more in-depth tonight before I do.
     
  9. NQ4T

    NQ4T XML Subscriber QRZ Page

    I forgot to post an update this weekend because I didn't actually do any development. I needed to spend less time in the computer chair so my back could recover from the previous week's bout of playing auto mechanic. I did decide I wanted to try and develop an actual iCom driver before I went too much further. I ultimately want this project to as modular as possible, so I figure rather than hard coding a bunch of things and then struggling to strip them out later; I was at a point where I should probably start making it look like my final version. I've largely been studying just how exactly one writes a driver for the Arduino IDE.
     
    KA9JLM likes this.

Share This Page