Serial Comms question

Hi All,

Quick background - im a communications engineer, so understand all the electronics, but im a hopeless programmer! So please bear with me,

Im working on a project that requires bidirectional serial data transfer between two Arduinos (in this case a nano and a mega). Im not going to start as complex as that though, and for now just need to get my head around transferring data from one of them to the other,

Now, the data in question is in the form of individual binary inputs but needs to be displayed as the decimal equivalent. Its not strictly binary though, as it comes from groups of four or five switch contacts in a 'sort' of BCD. Each switch represents one unit of a radio frequency, i.e. one switch will be the kHz value, another the 10KHz etc etc,

At the other arduino, or to start with on the serial monitor i guess, I need to display the actual decimal value, of all the switches, in order, to show the actual frequency,

as an example, imagine that the frequency 123.4kHz has the binary equivalent of 1111 1111 11111 111

you get the idea...

What im not at all sure about is how the data is transferred by a serial link. As I understand it, it is done one byte at a time? So am I right in thinking that I will need to do the conversion of the bianry to decimal before sending the values over the link, as decimal values?

So, for example - I would read the four input pins that connect to say, the nkHz switch, convert that to the decimal equivalent, then send that number by serial? Or do I read and convert all the switches until I have the whole set of decimal values for the full frequency, and send them all together as a large integer?

The goal here is to read all the frequency setting switches (there are 6 of them!), send the value to the other arduino, and display the decimal frequency on an LCD (which will be somewhere between 2,000.0MHz and 29,999.9MHz). This will create a frequency display remote to the radio that the switches are on.

I intend to start learning how to do this by setting up one Arduino with a set of DIP switches to mimic the radios frequency switches (just one at first) and get that sending data to the serial monitor, then move up to mimicing the whole switch bank and displaying the full frequency (around 19 inputs!), from there move to displaying on the LCD of the other arduino.

So any advice anyone can give on getting the first stage working would be very helpful!

Regards
Martin

Decimal is just a text representation of a byte (or int or ...). You can either convert to text at the sender or at the receiver.

Serial.print/ln will do the conversion for you, Serial.write will transfer as binary. The serial monitor will basically only display ascii (text).

You can collect the data for one set of dipswitches into a single byte (it fits in a nibble and hence two sets of dipswitches fit in a byte). You can also collect all dipswitches in a (unsigned) long (6x4 =24).

void loop()
{
  // read the dipswitches; data collected in a byte
  byte dip1 = readDip1();
  Serial.print(dip1);
}

If your dipswitches are e.g. set to 1001, the result in serial monitor will be the text "9".

You probably also only want to send data if there is a change in the dipswitches; there is a statechange detection example in the IDE. The trick is to remember the last reading and compare it to the current reading and only send something on change.

For reliable communication, you will need some form of packets where it's clear what the start of a packet is and what the end of a packet is; as a communications engineer that should be clear to you. You can have a look at Serial Input Basics - updated; it describes the receiving of packets from e.g. the serial monitor. Replace 'serial monitor' by 'other arduino' and you have the idea what to implement at the sender.

Hi, Martin.
Welcome to the Arduino Forum.

First, is there a good reason for only sending the switch settings rather than the actual frequency they represent?

Second, how often do you need to update the frequency display? That will determine the speed of data transmission you need.

Third, will you always have a fixed place for the decimal point in the frequency?

Paul

Many thanks for the swift replies chaps. Im guessing its daytime where you are (I note your US callsign Paul!), Im only awake as im working the nightshift,

Great info, thankyou. Yes the data will need to be packetized, this is in many ways the biggest headache for me - im very happy to move packetized data about over my RF comms links (its essentially now what I do for a living) but the nuts and bolts of actually forming those packets isnt my field.

It seems best that I do the conversion at the 'sending' end, where the input switches are, due to the slightly odd not 'quite' BCD format. It should be possible then to read all the switches, convert the BCD like format either direct to the equivalent number to to proper binary first, packetize and then send the whole frequency as a number.

The radio im doing this with has six frequency selection switches, representing 10MHz, 1MHz, 100kHz, 10kHz, 1kHz and 100Hz. Any valid frequency from 2MHz to 29.9999MHz can be set. As such, the resulting number from reading and converting the switch settings can be sent as an integer from 020000 to 299999. The decimal point can be added in just as a display artifact as its only needed to show the human user!

OK then, hopefully tonight I will get a single 4 or 8 way DIP switch set up and see if I can make it talk.

The ultimate goal of the project is bidirectional - the 'remote' arduino will read the switches and display the frequency on an LCD, but then a new frequency can be set on the remote and sent back, changing the input values from the switches to the radios RF synthesiser (the switches are already modified with diodes to allow this). This is a long way off though!

Another thought. Ive been looking at ways to provide a 'look-up' table for converting the actual switch reading to a number. This is a tad awkward as not only is it not actually binary nor normal BCD (as best I can work out), some switches are 4-bit, some are 5-bit, and one of them is only 2-bit!

For example, the 1MHz switch has the following 4-bit code for the value 5 - 1011, but the 100kHz switch has the 5-bit code 11000

I was looking at reading each pin in turn, then using if...else with boolian AND functions to convert to a decimal number. But it seems it might be easier to read the whole switch to return a single value (im guessing an ascii character in the case of some of the 5-bit) and then use a much simpler look up to get the right value.

Ive studied the logic table for these switches and I cannot see any standard code in use, but the radio was designed in the late 1970s for military use, and used some of the worlds first ever 3v logic, it isnt surprising that the code doesnt follow any obvious standard.

Would reading a 5-bit switch return a decimal number or a single ascii character? Or is that something I can choose to make it do how I want?

Reading 5 bits will give you a number; the interpretation of that number depends on you. You can interprete it as a number (byte, int) or as a ascii value (char).

It depends on what the mysterious ‘BCD dipswitch’ does; maybe posting the truth table will help. If it’s powered in some way, you also need to be careful mixing 5V and 3V logic. If you have a schematic how it is wired, that might help a lot as well (scan / photo of pencil drawing is fine).

You also need to be careful with floating inputs; the usual solution is the use of external pull-downs or internal or external pull-ups.

The below code will read 5 dip switches and store it in a byte. Dipswitches need to be wired between pin and GND.

byte dip1pins[] = {2,3,4,5,6};

void setup()
{
  Serial.begin(115200);
  for (int cnt = 0; cnt < sizeof(dip1pins); cnt++)
  {
    // all dips between pin and GND; when a dip is closed, it will read LOW, else HIGH
    pinMode(dip1pins[cnt], INPUT_PULLUP);
  }
}

void loop()
{
  byte val = 0;

  // read the pins and store each pin in its 'associated' bit in the val variable
  for (int cnt = 0; cnt < sizeof(dip1pins); cnt++)
  {
    val |= (!digitalRead(dip1pins[cnt])) << cnt;
  }
  Serial.print("value = ");
  Serial.println(val);
  delay(1000);
}

As explained, Serial.println(val) will send the decimal text representation of the number and not the number itself to the serial monitor.

Great, think im starting to get it now.

Im only using DIP switches to mimic the actual switches in the radio. These are active high rotary switches with either 2, 4, or 5-bit outputs. The truth table for them bears no resemblence to any standard binary code I can see, for example the three that are 5-bit have different truth tables to each other! A quirk of the 1970s military design I guess. (I cant seem to get a copy of it to upload im afraid)

For the time being, im just working with a single 8-way DIP switch, using just 5- of the switches to mimic the radios switch. This is just so that I can get it reading those 5- bits, convert them, and send them. I can then go through the code and ensure I thoroughly understand it before advancing.

Each bit of code you guys have posted for me ive then worked through and looked up each and every command and syntax, im slowly beginning to understand what does what and how it all goes together.

Probably the best way for you to see what im ultimately doing with this radio and what im up against would be to have a look at my blog http://g7mrv.blogspot.co.uk/ and look at the entries for 'Clansman PRC320 remote'

Hi.
Looked up your QRZ page and followed your blog for several weeks. Nicely done projects. You certainly do like challenges!

Glad to see you posted the id of the ex-military equipment you are designing for. Does the remote interface supply a "strobe" signal to tell when any of the switched have been changed and it's time to send the result to the remote? Same question in reverse for remote frequency selection? If not, could you design such a condition into the interface? What voltages are supplied to the remote interface?

As far as coding for transmission, I vote for true BCD. Two values per byte. Actually one value, 0 to 9, per nibble or "nybble" on your side of the pond. I worked for years on Burroughs Medium systems and they were based on an addressable 4 bit digit. The had to write 68000 assembly to handle data from a PC running COBOL. In emergency, all 4 bits can be used to give 0-F. This would require only 3 bytes to transfer the frequency.

73, Paul, KD7HB

Well, its early stages yet - ive to learn to code first! The last time I wrote software, other than some basic code for PICs, was in the late 80's/ early 90's, in BASIC!

The radio control has two parts, the 'local' controller, which will read the switches/load the synthesiser, plus several other control functions, and talks to the 'remote' controller, which will display the frequency on an LCD, take inputs from a rotary encoder, buttons etc, and from the radios PTT line. The system will work in two modes - 'local' in which the radios own switches control the frequency and the controllers do little more than act as a remote display, and 'remote', where the controllers will switch out the radios own frequency switches and take control of the synthesiser. This will be selected by a physical switch and the two modes are mutually exclusive - either its reading the radios switches, and displaying the frequency, or the switches are bypassed and the controllers run the synthesiser.

It is a complex project. The need to run at 3v is also awkward, but the radios own 3v supply line can be used for that. The remote controller can run at 5v as usual, the radios main 18-24v supply is available to run the remote via a regulator.

Ultimately, the switches will be read only if they change state (havent thought how yet), likewise, the updating of the 'local' controller when in remote mode will be done as and when the frequency is changed on the controller, or other functions occur such as the PTT, etc

My code will hopefully be functional, but it certainly will not be elegant!

Sorry I never got around to testing the code tonight - my laptop wouldnt load the drivers!

A quick note of thanks chaps,

Code example works perfectly. Im now looking at how to do the 'conversion' of the values returned by the switch inputs to the equivalent 0-9 decimal value. Shouldnt be too difficult, although im sure how I do it will be neither compact, elegant nor good practice! But it will help my understanding.

Once ive done that, i'll add the mock up of a second switch. Since I have an 8-way DIP on the breadboard,and two of the radios switches are 4-bit, i'll modify the code to do 2x4-bit reads and conversions.

I hope now to not have to post up endless posts on the forum, now I have a handle on it. But I will document my experience on my blog,

Many thanks again

Martin

When you start adding a second set of dipswitches, it’s advisable to use arrays or even better make use of an array of structs.

A struct creates a new type that combines variables that are related; it could be combining a first name, last name, address and telephone number for a phone book. Or in this case the pins for a dial and the value that you read from the dial.

I’ve renamed the original dipswitches to dial so it (more likely) reflects the true meaning.

// define the maximum number of pins of the dials
#define MAXPINS 5

// struct for dial reading
struct DIAL
{
  const byte pins[MAXPINS]; // dial pins
  byte readout;             // dial readout
};

// the dials (pin numbers are thumbsuck)
DIAL dials[] =
{
  {{2, 3, 4, 5, 6}, 0xFF },       // 5 pin dial; initial readout 0xFF (an invalid number for a 5 pin dial)
  {{7, 8, 9, 0xFF, 0xFF}, 0xFF }, // 3 pin dial; 0xFF indicates no pin
  {{10, 11, 12, 13, 0xFF}, 0xFF },// 4 pin dial
};

You can expand with more dials.

Next in setup() you can set the pinMode for all pins, one dial at a time

void setup()
{
  Serial.begin(115200);

  // for each dial
  for (int dialcnt = 0; dialcnt < sizeof(dials) / sizeof(dials[0]); dialcnt++)
  {
    Serial.print("dial "); Serial.println(dialcnt);
    // set the pin mode if a pin is specified (pin number not 0xFF)
    for (int pincnt = 0; pincnt < MAXPINS; pincnt++)
    {
      if (dials[dialcnt].pins[pincnt] != 0xFF)
      {
        Serial.println(dials[dialcnt].pins[pincnt]);
        pinMode(dials[dialcnt].pins[pincnt], INPUT_PULLUP);
      }
    }
  }
  Serial.println("ready");
}

Note the use of dialcnt < sizeof(dials) / sizeof(dials[0]) which allows ‘automatic scaling’; if you add a dial, you do not have to change anything in this part.

Also note that this still uses the internal pull-up as that matches my test setup.

Next in loop, you can read the dials in e.g. a for-loop.

void loop()
{
  // flag to indicate if one of the dials has changed
  bool hasChanged = false;

  // loop through the dials and read them one by one
  for (int dialcnt = 0; dialcnt < sizeof(dials) / sizeof(dials[0]); dialcnt++)
  {
    hasChanged |= readDial(dialcnt);
  }

  // if one of the dials has changed
  if (hasChanged == true)
  {
    // print the new values of the dials
    for (int dialcnt = 0; dialcnt < sizeof(dials) / sizeof(dials[0]); dialcnt++)
    {
      Serial.print("dial "); Serial.println(dialcnt);
      Serial.println(dials[dialcnt].readout);
    }
  }
}

And the function that reads a single dial

/*
  read a dial (still assumes use of internal pull-ups)
  in:
    the number of the dial to read
  returns:
    false if dial did not change, else true
*/
bool readDial(byte dialNumber)
{
  // temporary storage of reading
  byte val = 0;

  // read the pins and store each pin in its 'associated' bit in the val variable
  for (int pincnt = 0; pincnt < MAXPINS; pincnt++)
  {
    if (dials[dialNumber].pins[pincnt] != 0xFF)
    {
      val |= (!digitalRead(dials[dialNumber].pins[pincnt])) << pincnt;
    }
  }

  // compare current value with last stored value
  if (val != dials[dialNumber].readout)
  {
    // save as new readout
    dials[dialNumber].readout = val;
    // indicate that dial readout has changed
    return true;
  }
  else
  {
    // indicate that dial readout did not change
    return false;
  }
}

This would be my approach. If you have questions, ask. I wrote this as I think that it’s better if you start straight away with some sound basics instead of having to make massive changes later on when you start expanding.

Question:
which arduino are you going to use for the dials; I assume Mega as you want to read a lot of pins but you might have another approach.

Many, many thanks!

I am slowly working my way through to ensure I understand what each line is doing. Theres so many of the commands and functions that im very unfamiliar with, or that have changed in subtle ways since I last programmed in BASIC!

So far I have looked at the first two sections, and I 'sort of' understand how its working, although I have to admit that Arrays are something ive always had trouble with (except antenna arrays - those i do understand!). Ive even just had a lecture on C++ structure from my 13 yr old son! but he refuses to actually teach me!

Im going to play with these first two sections of code for a while before actually starting to read the pins.

And yes, its a Mega2560 im using, well a clone anyway

Hmmm, hit a snag,

Copied and pasted the code to read the dials, so I now have a sketch made up of the first three blocks of example code, save for mine now has six 'dials'

on compiling, I get the error that readDial is 'not declared in this scope'

now when ive seen that before ive added it somewhere as part of setup, ie byte readDial which I tried, assuming it was a byte variable, but then I get another error 'readDial cannot be used as a function'

Im now somewhat stuck. I'll keep researching and trying to figure it out though. Im sure it will be something very simple that im just not aware of yet

Post your code, sounds like a syntax and/or usage error.

Think ive got it!

After trying various ideas, I took a slow look through the code as I was suspicious that the term ‘Read’ in the ‘readDial’ might be the issue, and noticed that there didnt seem to be any command to actually read a D I/O pin! So I REM’d out the function and subbed in the command ‘digitalRead’ in place of ‘readDial’ -

for (int dialcnt = 0; dialcnt < sizeof(dials) / sizeof(dials[0]); dialcnt++)
{
// hasChanged |= readDial(dialcnt);
hasChanged |= digitalRead(dialcnt);
}

This compiled ok and all the ‘dials’ are returning 255 - exactly as i’d expect since all the pins are pulled up to Vcc and I have no actual switches physically present yet!

Pleas let me know if this is actually right as you intended. Its would be nice to think ive managed to debug the code all by myself - even if it was a very simple issue!

G7MRV:
Copied and pasted the code to read the dials, so I now have a sketch made up of the first three blocks of example code, save for mine now has six ‘dials’

on compiling, I get the error that readDial is ‘not declared in this scope’

readDial is in the fourth block :smiley:

As CrossRoads said, post the code if the above did not help :wink:

for (int dialcnt = 0; dialcnt < sizeof(dials) / sizeof(dials[0]); dialcnt++)
{
// hasChanged |= readDial(dialcnt);
hasChanged |= digitalRead(dialcnt);
}

That will not do the same thing; readDial reads all pins of a dial, your version only reads a single pin (with the pin number being the dialcnt).

Right, ok, I see the issue there,

I had misunderstood the two blocks of code as being alternatives, i.e. block C read all the dials, but block D just read a single specified dial,

I take it then that block D has to be incorporated into the sketch with the others to return the actual dial values (ive removed my added digitalRead)

I'll have a go. In the meantime, for if I cant figure it out, where would I incorporate it? As it contains the readDial definition then im thinking it needs to come before the use of readDial from block C, otherwise the program will come to that before the definition?

Im afraid that these days I find it difficult to work through things like this where I cannot see the entire thing at once! I suspect I might have to print it all out so I can run through it.

The next step then for me will be to work out how to add the necessary conversions from the returned values to the equivalent actual dial setting. I managed this using simple If... Else If... on my previous test sketch where I have a separate named variable for each dial. Its messy but it did work

Just dump the four blocks in sequence in the sketch.

In C/C++ readDial must be known to the compiler before it is used; you (or your son) are right there. But the IDE does some preprocessing before the actual compile and adds prototypes if needed (it sometimes fails though)

I thought it would be easier for you to understand if I broke it down. It was easier for me to write.

Next you need to show your conversion sketch so we can see how it can be incorporated.

Oh! Literally just dumped at the bottom!

It just didnt seem logical to me to do that. To my way of thinking, the definition had to be before in the code, i.e. above its use in the listing (reading Western style top down!). But I had noticed that when verifying, the compiler seemed to work from the bottom up!

Yes, it is much easier to understand in chunks, guess I need to understand a bit more about how the compiler works as well!

ok then. Before going any further and trying to add the conversions, for which the specific switch truth tables are needed, im going to play with it as it is with the DIP switches attached to see how it works in real life!

Im also going to print the sketch out so I can read through it properly!

I'll post up my initial conversion code later. It might prove awkward since each dial has entirely different truth table, as well as different numbers of connections, so a single conversion table cannot be applied to them all. But let me get me head fully around what we have so far first!

Cheers
Martin

Up and running on breadboard reading an 8 way DIP switch as a pair - 1x2-bit, 1x5-bit (equivalent to the 10MHz and 100kHz dial switches on the radio), I cant add any more physically at present as I dont have any more DIP switches to hand,

Heres the sketch for reading one switch and then converting the valid inputs (as from the DIP switch) to their decimal dial values. Each switch is different, there is no one conversion that will work on them all, a specific conversion step is needed for all six dial switches.

/* Test sketch for reading 4-bit switch, converting resulting value to 
 *  equivalent decimal switch setting, and sending to serial monitor. 
 *  Inputs are specific to switch type truth table and NOT binary
*/
// Set up initial variables
byte dip1pins[] = {2,3,4,5}; // DIP switch 1 connected to D I/O pins 2,3,4,5

void setup() {
  // set up serial comms
  Serial.begin(9600);
  // set pins to inputs. Loop does all four pins
  for (int cnt1 = 0; cnt1 < sizeof(dip1pins); cnt1++)
  {
    pinMode(dip1pins[cnt1], INPUT_PULLUP);
  }

}

void loop() {
  // read pins, store each pin in assigned bit
  byte val = 0; // resulting value
  byte MHZ = 0; // converted to decimal (equivalent to RT320 1MHz switch)
  for (int cnt1 = 0; cnt1 < sizeof(dip1pins); cnt1++)
  {
    val |= (!digitalRead(dip1pins[cnt1])) << cnt1;
  }
Serial.print("Value = "); // prints switch value to serial monitor
Serial.println(val);

// Next section converts the possible valid values to their decimal equivalents. Actual values in real system will depend on wiring from switch
// Note that in real system the 1MHz and 10MHz switches are to be read only
if (val==8)
{
  MHZ=1;
}
else if (val==1)
{
  MHZ=2;
}
else if (val==9)
{
  MHZ=3;
}
else if (val==5)
{
  MHZ=4;
}
else if (val==13)
{
  MHZ=5;
}
else if (val==3)
{
  MHZ=6;
}
else if (val==11)
{
  MHZ=7;
}
else if (val==7)
{
  MHZ=8;
}
else if (val==15)
{
  MHZ=9;
}
else
{
  MHZ=0;
}
delay(1000); 
Serial.print("MHZ = "); // print equivalent decimal to serial monitor
Serial.println(MHZ);
}

In this case, each switch has its own named variable that I can work on. Im thinking that, just as the pin mode setting etc was done via a loop counting the dials, then a similar loop is needed, where each dial will branch to its specific conversion code.

I will try and get a copy of the truth tables posted, as without them, the inputs dont make any real sense