• You can now help support WorldwideDX when you shop on Amazon at no additional cost to you! Simply follow this Shop on Amazon link first and a portion of any purchase is sent to WorldwideDX to help with site costs.

uPD 858 ssb chassis arduino control

And now I'm getting nothing on the channel display. I had seen a promising change on the ones digit looking almost right with an extra segment lit, then nothing. MCP23017 will no longer respond.

So now I'm stuck with a completely dead project until I can get some parts in.

Only reason I can think that the first one went so easy was too fool me into thinking the second one would be as well.
 
My crappy wiring job is the most likely culprit. I received some new MCP23017's this week, and started building replacement boards for the display and PLL interfaces. Hooked them up on a breadboard to make sure they both worked. Luckily they did, or at least didn't throw error messages.

I did notice that the display ones digit had the same problem as before, one extra segment lit. Tracked it down to a trace on the strip board that didn't get cut all the way through. I thought I had checked that, but apparently not well enough. Cut it, again, and retested. Ones digit now working perfectly, just like the tens digit.

Don't know if I'll get both of these installed into the radio this weekend or not. But at least now I have working chips. The dead ones were holding up progress a bit.
 
Had some time to hook these up in the radio this morning and... nothing works.

I've got the 5 volts at each of the boards, so I guess I need to see if the i2c bus is somehow broken. I had really thought that the MCP23017's were the problem since I had one working and one not, and then the working one quit.

The Arduino itself appears to be OK, it's reading the channel selector and telling me what the Ncode would be if it could talk to the PLL. But somewhere between there and the new chips the connection just isn't happening.
 
So, progress of a sort. I had the two chips connecting to the Arduino on the i2c bus in a spoke configuration. Which sounds weird, since there was only two spokes. Well, apparently i2c doesn't take too kindly to that. Set them up so that the bus first goes to one chip, then the other. Boom, I've got tuning working.

I had set the first radio up that way, but thought I had done the spoke thing. Oh well, I've wasted more time and money on more trivial problems. And it reinforced the lesson, "If you've got one that works look at it, stupid!"

Discovered by accident that if I very quickly cycle power on the radio that channel display will also pop up. So I can see where I am, which is nice.

I wired up the two already hijacked switches (old channel mods) to be a "band" and a +10Kc switch. The band switch does switch bands, but not where I want it to. That's my fault, obviously. I wrote the code before I figured out which switches were already played with. Thought I would do uppers and lowers. Now I've got lowers, but I want uppers. The +10Kc I was trying a little trick I thought of for faster switching, it's a little too fast and keeps jumping back and forth between the normal and +10Kc channel. So I'll have to "unoptimize" that little section of code to fix it.

So that's one hardware and two software problems left to fix. The first radio didn't get the band switch or +10kc switch because all of the front panel switches are wired to perform their original function, and I didn't want to screw it up. I can always rig something with a small external box and some toggles later.

More pics when I get the code straightened out so you can see it proudly displaying channels above 40. I may even move some of the crap on my bench so I can show that the frequency counter matches the channel display.
 
Haven't given up on this, although I probably should have by now. Caught some kind of nasty virus last week and I'm just now starting to actually feel better. I did bolt prototype #1 back together, only to find out I may have plugged the RX LED in backwards. Well, it's that or I blew it up somehow.

I did snap one pic before closing:
IMG_2125.jpeg
Near the top you can see the 5 volt regulator bolted to the case. It runs warm, but why take the chance? Yes, I insulated it. Silpad for the win!

Arduino wrapped in some bright orange heat shrink, because the XYL likes the color.

This is the one that didn't have pre-screwed with switches to hijack, so it operates like stock until I find a project box to put some toggles into.

Prototype #2 is still being a pain in the rear. I had everything working for about five minutes, then it went wonky again. If I had stopped at #1, though, you guys would have nothing to point at and laugh about.
 
I've found that if I can play with certain timings on power up on radio #2 I can get it to work. Which tells me I still have to figure out something on the i2c bus. In the meantime, I'm going to start dumping code on you guys to abuse or ignore as you see fit.

Now, could this code be better optimized? You bet! But since this is a hobby project and I'm not a programmer (nor do I play one on TV), I just can't be bothered.

First up, well look at the includes and pre-set setup() definitions:

// Arduino sketch to control the uPD858 SSB chassis from Uniden.
// This is the chassis that the following radios are known to be built on:
// Cobra: 138 XLR, 139 XLR, Realistic: TRC-449, TRC-457, TRC-458,
// Robyn: SB-510D, SB-520D, President: Adams, old Grant, old Madison, old Washington
// Teaberry: Stalker 101, Stalker 102, Stalker 202
// Could be adapted to many radios, if you're willing to put in the work.

// Same concept could be used to adapt any 40 channel swtich to control the PLL in a
// 23 channel radio (for those that have PLLs).


// Hardware used to build the prototype.
// 1 x Arduino Pro-mini
// 2 x MCP23017
// 2 x 4.7K ohm resistors
// 22 x 1K ohm resistors
// 1 x Robyn SB-510D


Yes, that's a lot of comments before you actually DO anything. And the reason I specify one Robyn here is I wrote this for the first radio (that works) and never updated it for the second one.

// Pull in library to control MCP23017 chips
#include <Adafruit_MCP23X17.h>


This is the library file that contains the routines that allow the Ardiuno to communicate with the MCP23017 chips.

// Create two instances so that the right data gets sent to the right MCP23017
// mcp1 is connected to the PLL, mcp2 is connected to the channel display
Adafruit_MCP23X17 mcp1;
Adafruit_MCP23X17 mcp2;


Mostly self-explanatory. You'r telling the Arduino there's two MCP23017 chips to talk to. Since Arduino is C++ under the covers, you're basically creating two instances so that the library code can be told which one to address when.

Now we get into arrays:

// Set up an array containing all possible inputs from the channel selector.
byte CHS[41][8]= {{0,1,0,1,0,0,0,0},{0,1,0,1,0,0,0,1},{0,1,0,1,0,0,1,0},{0,1,0,1,0,0,1,1},{0,1,0,1,0,1,0,1},{0,1,0,1,0,1,1,0},{0,1,0,1,0,1,1,1},{0,1,0,1,1,0,0,0},{1,0,0,0,0,0,0,0},{1,0,0,0,0,0,0,1},{1,0,0,0,0,0,1,0},
{1,0,0,0,0,0,1,1},{1,0,0,0,0,1,0,1},{1,0,0,0,0,1,1,0},{1,0,0,0,0,1,1,1},{1,0,0,0,1,0,0,0},{1,0,0,1,0,0,0,0},{1,0,0,1,0,0,0,1},{1,0,0,1,0,0,1,0},{1,0,0,1,0,0,1,1},{1,0,0,1,0,1,0,1},
{1,0,0,1,0,1,1,0},{1,0,0,1,0,1,1,1},{1,0,1,0,0,0,0,0},{1,0,0,1,1,0,0,0},{1,0,0,1,1,0,0,1},{1,0,1,0,0,0,0,1},{1,0,1,0,0,0,1,0},{1,0,1,0,0,0,1,1},{1,0,1,0,0,1,0,0},{1,0,1,0,0,1,0,1},
{1,0,1,0,0,1,1,0},{1,0,1,0,0,1,1,1},{1,0,1,0,1,0,0,0},{1,0,1,0,1,0,0,1},{1,0,1,1,0,0,0,0},{1,0,1,1,0,0,0,1},{1,0,1,1,0,0,1,0},{1,0,1,1,0,0,1,1},{1,0,1,1,0,1,0,0},{1,0,1,1,0,1,0,1}};


This first array is the binary output of the channel switch at each position, plus 1 to make the math work easier. I put the additional one at the front of the array to be in the "zero" slot. This is because in the Arduino environment counting starts at zero by default. You can work around that, but then you have to keep track of it everywhere. So I did it the lazy way.

// The NCODES to program the PLL have some "unique characteristics" on the FCC allotted CB channels.
// This array accounts for the channel "skips" and out of order configuration of channels 23, 24, and 25.
int FCC[41] = {90,91,92,93,95,96,97,98,100,101,102,103,105,106,107,108,110,111,112,113,115,116,117,120,118,119,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135};


Here I call the NCODES that program the PLL as integers rather than binary numbers. This is because the 858 is programmed in 10 digit BCD numbers. Again, I'm lazy so I did this instead of creating a 10 digit BCD array. Plus it makes life easier when going outside the FCC designated channels (for receive only or educational purposes, of course).

// This array has the data for digits 0 through 9. It is also set up for a common anode display, which means the whether a segment is on or not depends on whether it is grounded.
byte BINSVNSEG[10][7] = {{1,0,0,0,0,0,0},{1,1,1,1,0,0,1},{0,1,0,0,1,0,0},{0,1,1,0,0,0,0},{0,0,1,1,0,0,1},{0,0,1,0,0,1,0},{0,0,0,0,0,1,0},{1,1,1,1,0,0,0},{0,0,0,0,0,0,0},{0,0,1,0,0,0,0}}; // Segment order msb to lsb{g,f,e,d,c,b,a}


Here you get to see where I screwed up. I set the segment order backwards from what it should be. So I compensated in software. If you'd rather use some BCD to seven segment decoder latches you can. Which means you could drop this array and use the next one for channel display and PLL programming.

// This array is just 0 through 9 in binary. The sketch converts NCODES to BCD later on, and this comes in handy.
byte BCDDIG[10][4] = {{0,0,0,0},{0,0,0,1},{0,0,1,0},{0,0,1,1},{0,1,0,0},{0,1,0,1},{0,1,1,0},{0,1,1,1},{1,0,0,0},{1,0,0,1}};


It's literally what says in the comment. Allegedly the MCP23017 will automatically convert integers to their digital equivalents, but I didn't want to go to the trouble of laying out each possible BCD digit, converting that what those digits would represent in binary, then building an array for that.

// In case I can be bothered into doing a "band" display option, A, b, and C.
//byte LETSVNSEG[3][7]={{0,0,0,1,0,0,0},{0,0,0,0,0,1,1},{1,0,0,0,1,1,0}};


I haven't around to figuring out a good way to use this yet.

// Set which pins are being used for input that aren't part of the channel selector.
int SW1IN = 10;
int SW2IN = 11;
int SW3IN = 12;


This will be the "band" and +10kc switches

// Grouping all the channel selector pins together
//byte CHSIN[8] = {9,8,7,6,5,4,3,2};
byte CHSIN[8] = {2,3,4,5,6,7,8,9};


You see two possibilities here, because again I screwed up and wired the two radios differently. So I compensated in software.

The rest of this is just setting variables the sketch uses to set everything for FCC channel 1 by default.

// Setting channel 1 as the default. Can't get off channel 1? Channel selector may be disconnected or broken.
byte CHSEL[8] = {0,1,0,1,0,0,0,1};

// Set FCC band as default
int BAND = 2;

// Set all switches off as default.
int SW1 = 0;
int SW2 = 0;
int SW3 = 0;

// Set all other variables to be on channel 1 as default.
int CHNUM = 1;
int CHAN = 1;
int NCODE = 91;

int DISPCHAN = 1;
int SAVENCODE = 91;

int PLLONES = 1;
int PLLTENS = 9;
int PLLHUND = 0;

int ONES = 1;
int TENS = 9;

// Set the +10kc logical switch to off, just in case.
boolean PLUS10 = false;


When the sketch comes up, it will immediately set the radio to FCC channel 1, then read the channel selector and other switches and set the radio for whatever channel those tell it to go to. In practice, you usually don't see the channel 1 unless the radio is actually set there or something's broken.

Next we'll get into the setup() routine.
 
The setup() routine is fairly simple. It runs once at power up and then hands off to the main() loop.

void setup() {
// put your setup code here, to run once:
Serial.begin(115200); // for testing and debugging


If you're going to be doing any monitoring of the Arduino via the serial interface, make sure you set the sketch and the device that will talk to it to the same speed. Otherwise you get gibberish or nothing and will think something's broken when it's not.

// Initialize all the pins we'll be using for input as inputs.
// Makes it easier to read them that way.
for(int a=0;a<8;a++){
pinMode(CHSIN[a], INPUT);}


Sets all you channel selector pins as inputs

pinMode(SW1IN, INPUT);
pinMode(SW2IN, INPUT);
pinMode(SW3IN, INPUT);


Sets that other switch pins to inputs.

// Initialize the two MCP23017 chips. I forget where I
// stole the idea of printing an error message if the
// chip doesn't respond.
if (!mcp1.begin_I2C(0x20)){
Serial.println ("ERROR on PLL MCP");
}
if (!mcp2.begin_I2C(0x21)){
Serial.println("ERROR on DISPLAY MCP");
}


This is where you tell the library that talks to the MC23017 chips which instance is associated with which i2c address.

// Set the two MCP23017 chips so that all of the pins are outputs.
//mcp.pinMode(PIN_NUMBER_OR_NAME, OUTPUT);
for (int PIN=0;PIN<16;PIN++){
mcp1.pinMode(PIN, OUTPUT);
mcp2.pinMode(PIN, OUTPUT);
}


This little bit just cycles through the MCP23107 chips, setting all pins as outputs. Since they can be either, it is kind of important to do this.

// Initial configuration to set the right channel in case someone
// turned the dial or played with the switches while the radio
// was off.
readchsel();
readband();
setchan();
writepll();
writedisplay();


This is the initial reading of the channel selector and other switches which then sets the operating channel and then updates the channel display and PLL programming pins. Each of these little bits is a routine unto itself that we'll look at later.

Serial.println("Setup complete.");

Leftover debugging statement. How I got it to freeze up and not finish the setup routine I don't think I'll ever understand.

}

End of the setup routine. If you're getting weird error messages when attempting to upload code, you may have left one of these little guys off the end of a routine somewhere.
 
The readchsel() routine is very simple. It has to be so I can understand it.

void readchsel(){
// read the channel selector input bits and store into an array
for (int CHIN=0;CHIN<8;CHIN++){
CHSEL[CHIN] = digitalRead(CHSIN[CHIN]);
}


// Serial.println(CHSEL[CHIN]);

So all this statement really does is loop through the channel selector pins and read if they're high or low and then loads that info bit by bit into an array. The commented out line is just a debugging statement so I could see what the data was. It was in the loop at one time, now its outside the loop because I simplified the loop but didn't want to throw the debug statement away just yet.


// loop through another array to get index matching channel selector bits
for ( int CHNUM=1;CHNUM<41;CHNUM++){
if (memcmp(CHSEL, CHS[CHNUM], sizeof(CHSEL))==0){
CHAN=CHNUM;
}
}
// Serial.print("Channel ");
// Serial.println(CHAN);


This looks a bit more complex, and I'm sure it is you dive into the structure of the memcmp function. Which I'm not going to do since I have no idea how it works, just that it does. What we're doing here is taking the contents of the freshly loaded CHSEL array and comparing that to all members of the CHNUM array until we get a match. When a match is found, that position in the CHNUM array is our channel number.

}

Yep, we're done with this one.
 
The "bands" on this sketch are just the immediate 40 frequencies above and below the FCC band, with no skips. Easy enough to do when you control the program.

void readband(){

// Read the states of the switches and set the "band" accordingly.
// Only two switches are used here and if they're both either in or out
// you'll go back to the FCC 40 channels. It's a CB, asking it to cover
// 120 channels is enough of a stretch for it.
SW1=digitalRead(SW1IN);
SW2=digitalRead(SW2IN);

if (SW1 == 1){
if (SW2 == 1){
BAND = 2;
}else{ BAND = 1; }
}else{
if (SW2 == 1){
BAND = 3;
} else { BAND = 2; }
}
}


So, to me this is simple, but just in case you're having trouble following along...

If you have all switches off, normal FCC 40. If you have only switch 1 on, 40 below 1. If you have only switch 2 on, 40 above 40. If you have both on, normal FCC 40 channels. Why did I do it this way? Like the code comment says, this is a CB radio. It's not designed to cover what I'm asking here, much less another band of 40 channels. Can the chassis be broadbanded to cover that far? Probably. But there's a tradeoff to doing that in which the radio loses selectivity. I have seen one mod where the radio had extra varactors in the IF section to allow for greater range, but I'm not smart enough to figure out how that trick was done.
 
The setchan() routine is probably the heart of all this, beyond the hardware itself. This is where we do the math to figure out what channel we're going to be on.

void setchan(){
// What set of frequencies to use depends on the "BAND"
// In this case "BAND 1" is 40 below FCC channel 1, "BAND 2" is the standard
// FCC 40 channels, and "BAND 3" is the 40 above FCC channel 40.

// Also note that the channel display will go to 80 on "BAND 3". Sorry, only two digits to work with
// so no negative numbers or stuff like that.
if ( BAND == 1 ){
NCODE = 50 + CHAN;
DISPCHAN = CHAN;
}

if ( BAND == 2 ){
NCODE = FCC[CHAN];
DISPCHAN = CHAN;
}

if ( BAND == 3 ){
NCODE = 135 + CHAN;
DISPCHAN = 40 + CHAN;
}


As you can see, for anything outside of the FCC designated 40 channels, it's just simple math of "what band is set? + what's the channel selector set to? = channel". In the FCC designated channels we look up the channel in an array to get the PLL NCODE, which has the skips and out of order channels already set.

// For the heck of it, a +10 kC switch
// Notice that it doesn't change the channel readout? Again, only two digits so no "A" on
// the channel display. May add it later as an alternating display between the channel and
// a letter "A".
SW3 = digitalRead(SW3IN);
if (SW3 == 1){
PLUS10 = true;
NCODE = NCODE + 1;
}else{
PLUS10 = false;
}


The +10kc switch is easier to implement this way than in hardware. We just increment the NCODE value by one. Which is easy to say when I've already significantly modified the hardware.

}
// Only call the channel setting routines if there's something for them to do
if ( NCODE != SAVENCODE ){
writepll();
writedisplay();
Serial.print("NCODE: ");
Serial.println(NCODE);
}


No point in continuously updating the PLL programming pins and channel display if nothing has changed. This might even help reduce the amount of digital noise introduced by this mod. Probably not, but it might.

// In order for the sketch to know that the channel selector has changed, it needs to know what the value was the last time through the loop.
// That's what SAVENCODE is for.
SAVENCODE = NCODE;
// Serial.println (NCODE);
}


This last little bit is just to make sure we know where we were to see if we've moved at all.
 
Of course, we need to write the data out to the PLL so we can actually operate on our chosen frequency. I was already thinking about posting this here by the time I wrote this bit of code, so the explanation is more or less contained in the code comments.

void writepll(){
// PLL is BCD programming, so we have to convert the lower parts of the
// programming from int to BCD. Rather than write a function that is
// of dubious quality, just split out the ones, tens and hundreds.
PLLONES = NCODE % 10;
PLLTENS = (NCODE / 10 ) % 10;
PLLHUND = (NCODE / 100) % 10;


I will pause the flow here to explain the odd looking math above. In addition to a straight division by 10 and 100, I'm using something here called a "modulus". What this does is give the remainder of a division operation as the answer. So, if I divide 95 by 10, the modulus is 5. I do this for each BCD range (0-9, 10-90, 100-900) so that I get just the bit I want for each.

// Then use the BCDDIG array above to get the binary values needed.
for (int BIT=0;BIT<4;BIT++){
int OBIT = BIT+4;
int HBIT = BIT+8;
mcp1.digitalWrite(OBIT, BCDDIG[PLLONES][BIT]);
mcp1.digitalWrite(BIT, BCDDIG[PLLTENS][BIT]);
mcp1.digitalWrite(HBIT, BCDDIG[PLLHUND][BIT]);
// Serial.println(OBIT);
// Serial.println(BIT);
// Serial.println(HBIT);
// This should probably be explained. The sketch is trying to send 4 digit BCD
// numbers to the MCP23017, which is a 16 bit device with two 8 bit addressable registers. Fortunately we can access
// each of those 16 bits (nearly) directly.
// The reason the "ones" bits are higher bit postions on the MCP23017 than the "tens" bits is simple,
// the chip fit better that way. What I mean by that is that it easier to physically fit the chip in line with the PLL
// programming pins.

}


From here on in it's wiring notes so I could just look at the screen to help me keep things sorted instead of having to go back to the hardware or a sheet of paper every few minutes.

// MCP23017 to uPD858 wiring, at least if you use this sketch without much editing.
// MCP23017 ---- uPD858
// 28 13
// 27 14
// 26 15
// 25 16
// 24 17
// 23 18
// 22 19
// 21 20
// 4 21
// 3 22

// Power to the channel selector is on pin 9 of J400 at 4.68 Volts. Ground is pins 11 and 12 of J400.
// J400 to 858 wiring
// J400 ----- 858
// 1 13
// 2 14
// 3 15
// 4 16
// 5 17
// 6 18
// 7 20
// 8 21

}
 
Then we need to write to the channel display. You did want to at least see something so you don't have to completely guess which channel you're on, right?

void writedisplay(){
// This is really simple. Split the channel number to display into a ones digit and a tens digit.
// Then use those results to find the correct binary seven segment display code at an array location
// for each and write those out to a MCP23017 that's hooked to the seven segment displays.

ONES = DISPCHAN % 10;
TENS = (DISPCHAN / 10) % 10;
// Serial.print("ONES ");
// Serial.println(ONES);
// Serial.print("TENS ");
// Serial.println(TENS);


Here you can see I was using the modulus function again. I had tried something else, this was much cleaner.

// If there's no reason to display a digit in the "tens" column, don't show one.
// Just blanks it our for channels 1 through 9. Otherwise, light it up!
if (TENS == 0 ){
byte BLANK[8] = {1,1,1,1,1,1,1};
for (int TBIT=8;TBIT<15;TBIT++){
int BIT = TBIT - 8;
mcp2.digitalWrite(TBIT, BLANK[BIT]);
}
}else{
for (int BIT=8; BIT<15; BIT++){
int TBIT = BIT - 8;
mcp2.digitalWrite(BIT, BINSVNSEG[TENS][TBIT]);
}
}
for (int BIT=0; BIT<7; BIT++){
int TBIT = BIT - 8;
mcp2.digitalWrite(BIT, BINSVNSEG[ONES][BIT]);
}


This is just writing out the tens and the ones digits on the channel display and blanking out the tens digit if would display a zero. Everything past this is more wiring notes. BTW, the dimmer function does still work on the modified radios.

// Pins 4 and 5 of the TLR-321 or tied together and to the supply voltage. In the 858 chassis
// that supply voltage comes from TR36 via pin 10 of J400. Which is a fancy way of saying
// through the ribbon connector. TR36 is what makes the channel display dimmable. This
// will still work if you connect it up. At least, it works for me.
// Ground is pin 11 and 12 of J400.
}
 
Finally, the main loop. As you can plainly see, it's about as simple as I could make it. It just calls the readband() function to read switch settings and set variables based on those, then readchsel() to get the current channel selector position and set the CHAN variable. Once those are done it calls setchan() which figures out if anything has changed and will set the PLL programming and channel display appropriately.

void loop() {
// Extremely simple. Get the current setting of the
// channel selector, then the "band" switches. Feed that
// data to the setchan function. If it detects any change
// it will update the channel display and PLL programming
// to match.
readband();
readchsel();
setchan();

// debug
// Serial.println("I'm alive");
// delay(1000);
}
 
So, you may ask yourself, who cares? Hell if I know. I did this for several reasons.

1. It's possible to take what might have otherwise been a parts chassis due to that darn flexible circuit board failing and give it a new lease on life.

2. Despite what others may have posted or said about it elsewhere, it's pretty easy to get the 858 ssb chassis on channel 41 (27.415 MHz). You just have to understand how PLL programming works.

3. Possibly inspire someone to try this on a different chassis, or to "fix" a radio using a different channel selector than what it came with stock.

4. I was bored and thought it might be fun. Which, really, falls under, "We do these things not because they are easy, but because we thought they would be easy!"
 

dxChat
Help Users
  • No one is chatting at the moment.
  • @ AWP:
    Is it possible to be on a lake and have a homing directional beam being emitted from the shore so a person could navigate to that beam's source? For example at night to a jetty.
  • @ BJ radionut:
  • @ wavrider:
    sea que sea que,
    +1