Writing parallel ASCII data from an UNO[solved]

Hello, Forum! This is my first post. :slight_smile:

I have an extensive background in electronics and engineering, with considerable experience writing programs in GWBASIC, but I am a total Noob to the Arduino and the C++ language! :slight_smile: I built an interface from my UNO to a 4-character alphanumeric display, and I have verified that the interface is working 100%.

I've Googled the Forum to find out how to write parallel data to the pins on my UNO, but none of the answers I found even come close. (Most refer to using the whole ports, but I can't, since I need the serial/USB, etc.)

My setup is using pins 2-9 for parallel data, 10-12 for display address, and pin 13 to write data to the display.

What I want to do is put messages up onto the 4-character display. I need to figure out how to loop and convert ASCII characters into parallel bits, to be written across the data pins in parallel. (Doing it sequentially, a pin at a time, is working fine, as the data is only written when I strobe pin 13 low.)

For now, I've tested the display by using a FOR loop to digitalWrite all pins LOW, then use individual digitalWrite commands to set pins high to display one character at a time, with lots of COPY/PASTE and changing HIGH to LOW, etc. (This is cumbersome.) :wink: I need a loop to send messages to the display, one char at a time, incrementing the address (pins 10,11) and pulsing pin 13 for each. (I have it displaying a 3-word message, now, 4 chars at a time.)

Code snippets would be very much appreciated. :slight_smile: (No writes to ports, DDR, etc, as I prefer not to mess things up on the UNO board.) I can piece together a program to parse and display strings of characters, once I have examples of how to grab each byte and write it to the pins.

So far, I like the Arduino... but the learning curve for C++ is a bit intimidating! (I sure miss having GOSUB and RETURN!)

Thank you! :slight_smile:

Willie...

The "LiquidCrystal" library does it like this:-

// Constructor (Arduino pin numbers in d0 to d7):-
LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable,
     uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
     uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7)
{
  init(0, rs, rw, enable, d0, d1, d2, d3, d4, d5, d6, d7);
}
.
.
.
// In 'init()':-
  _data_pins[0] = d0;
  _data_pins[1] = d1;
  _data_pins[2] = d2;
  _data_pins[3] = d3; 
  _data_pins[4] = d4;
  _data_pins[5] = d5;
  _data_pins[6] = d6;
  _data_pins[7] = d7;
.
.
.
// In 'write8bits()', (entered with value to be written in 'value'):-
for (int i = 0; i < 8; i++)
{
    pinMode(_data_pins[i], OUTPUT);
    digitalWrite(_data_pins[i], (value >> i) & 0x01);
}

Doing it sequentially, a pin at a time, is working fine

This is, essentially, the only way to do things with Arduino. Of course, you can put it all inside a function and make it less "cumbersome" (as shown in the code from the LiquidCrystal library)
If that turns out to be too slow (which is pretty unlikely, for a human-read display), you could get a significant speedup by storing arrays of ports/bits derived from pin numbers, instead of just pin numbers, which would bypass a lot of redundant logic in digitalWrite()

And yes, it is unfortunate that there aren't any contiguous 8-bit ports that can be used :frowning:

If you pick two Ports that, between them have enough pins then you should be able to use port manipulation to write the data.

You will have to split the data between the Ports and use SHIFT to get them in the right place within each byte, but it should be much quicker than digitalWrite() which is a slow function.

Also make sure to use &= and |= so that you don't change the bits in the Port that should not be changed.

The Mega has some unused Ports :slight_smile:

...R

You do realize, I hope, that you can write a value from a variable using digitalWrite()? e.g.

digitalWrite(pinX, value);

I say this because you made vague reference to a lot of cut and paste of HIGH and LOW etc.

Robin2:
If you pick two Ports that, between them have enough pins then you should be able to use port manipulation to write the data.

You will have to split the data between the Ports and use SHIFT to get them in the right place within each byte, but it should be much quicker than digitalWrite() which is a slow function.
Also make sure to use &= and |= so that you don't change the bits in the Port that should not be changed.
The Mega has some unused Ports :slight_smile:
...R

That's how I'd personally do it too. I avoided this method because Willie--- said "No writes to ports, DDR, etc", but that raises a question - why not?

The full sentence was:-

Willie---:
No writes to ports, DDR, etc, as I prefer not to mess things up on the UNO board.

@Willie---, what do you mean by "mess things up"? Done properly, using & and | as mentioned by Robin, it won't mess anything up, and would definitely be more efficient then the "LiquidCrystal" method that I posted.

For a tiny text display, speed really is not that critical. I would suggest just using digitalWrite() so you can get this done and move on to the next fun project all the sooner. You won't see any difference on the display.

Willie---:
I need to figure out how to loop and convert ASCII characters into parallel bits, to be written across the data pins in parallel.

The function to read single bits from a byte in Arduino is bitRead()

Bit numbering of a 8-bit variable is from bit-0 (low-bit) to bit-7 (high-bit) or the other way round if you like. Just use a for-loop to deal with each bit of the ASCII character.

Being a total NOOB to this... (forgive me, and don't laugh) this is how I implemented my simple 3-word message. This is only the FIRST WORD, "Good" in which I could take a "shortcut" since the 2'nd and 3'rd characters are the same. :slight_smile: The other two words are done the same, cumbersome way...

// First word, "Good"
digitalWrite(2, HIGH); //Set Leftmost char "G" (lo bit)
digitalWrite(3, HIGH);
digitalWrite(4, HIGH);
digitalWrite(5, LOW);
digitalWrite(6, LOW);
digitalWrite(7, LOW);
digitalWrite(8, HIGH);
digitalWrite(9, LOW); // (hi bit)
digitalWrite(10, HIGH); //Set address (lo bit)
digitalWrite(11, HIGH); //Set address (hi bit)
delay(5);
digitalWrite(13, LOW); //write it (Pulse 13)
delay(5);
digitalWrite(13, HIGH);

digitalWrite(2, HIGH); //Set 2'nd & 3'rd char "o" (lo bit)
digitalWrite(3, HIGH);
digitalWrite(4, HIGH);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
digitalWrite(9, LOW); // (hi bit)
digitalWrite(10, LOW); //Set address (lo bit)
digitalWrite(11, HIGH); //Set address (hi bit)
delay(5);
digitalWrite(13, LOW); //write it (Pulse 13)
delay(5);
digitalWrite(13, HIGH); 
   // Write 3'rd char (same as 2'nd) by changing address
digitalWrite(10, HIGH); //Set address (lo bit)
digitalWrite(11, LOW); //Set address (hi bit)
   //That was the shortcut! ;)
delay(5);
digitalWrite(13, LOW); //write it (Pulse 13)
delay(5);
digitalWrite(13, HIGH);

digitalWrite(2, LOW); //Set last char "d" (lo bit)
digitalWrite(3, LOW);
digitalWrite(4, HIGH);
digitalWrite(5, LOW);
digitalWrite(6, LOW);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
digitalWrite(9, LOW); // (hi bit)
digitalWrite(10, LOW); //Set address (lo bit)
digitalWrite(11, LOW); //Set address (hi bit)
delay(5);
digitalWrite(13, LOW); //write it (Pulse 13)
delay(5);
digitalWrite(13, HIGH);
delay(500); // Wait 1/2 second
   //Second word, "Job!"

Being able to replace the HIGH and LOW with variables, where I can grab the bits out of each ASCII character would be great!

I appreciate the suggestions I've seen so far... I only posted this, because I saw some questions about what I meant, and what I was doing, and my use of CUT and PASTE to make this program. (Now you see what I mean.)

Speed is certainly the least concern, as I'm just LEARNING something VERY new to me. :slight_smile: (I'm 53 years old, so old dogs CAN learn new tricks, with patience!)

Thank you, everyone! I will be testing some of your suggestions, and will let you know how it turns out. :slight_smile: I reiterate that I am VERY reluctant to play with the DDR's right now, because one mistake might make life very difficult, should the USB/serial interface be trashed. One step at a time... I'll get there, and gain the confidence to wade in deeper. :slight_smile:

If you want to see this very basic system in operation, check out this video:

(It is on Facebook, and should be viewable without a FB account.)

Again, THANK YOU!! :smiley:

Willie...

You're on the right track, just use the advice in reply #7. Also if it is not too vain to suggest, reply #4.

Then when it works, try to make another version with Port Manipulation. You really can't mess up your serial with it. Not permanently.

Ah, yes! Reply #4! Thanks, aarg! :smiley:

This is MUCH better, and I used the FIND/REPLACE ALL feature of the IDE to convert my code, thus:

digitalWrite(2, 1); //Set Leftmost char "G" (lo bit)
digitalWrite(3, 1);
digitalWrite(4, 1);
digitalWrite(5, 0);
digitalWrite(6, 0);
digitalWrite(7, 0);
digitalWrite(8, 1);
digitalWrite(9, 0); // (hi bit)

It works... PROGRESS! This NOOB is learning!! :wink:

Next step... using a FOR loop to cycle thru the pins and the bits! Maybe I'll use the word "Noob" next? :wink:

Willie...

I hate to dampen your enthusiasm, but that was a giant step backwards. HIGH and LOW are preferred to 1 and 0. What you need to do, is substitute a variable for your constant 0's and 1's.

Sorry, but...

Also, it's about time you gave your pins names that correspond to the actual pin assignments, like:

digitalWrite(addressPin2, someVariableName);
digitalWrite(dataPin0, someVariableName);

as was indicated in reply #4. The variables are values that you obtain using the methods from reply #7.

It's a step forward, in that I'm learning something new, and VERY different from the GWBASIC and 6502 machine language with which I am most familiar. :slight_smile: It's far easier for me to edit 1's and 0's than "HIGH" and "LOW", but yes, I understand what you're saying.

Baby steps... :slight_smile:

Willie...

You say that now, just wait until an "l" or an "|" slips in where you meant 1, or an "O" replaces an "0".

And it is far easier to do a find or search and replace, for example, on HIGH and LOW than 0 and 1, because it will find every instance of a 0 or 1 in every number in your entire program. Think about that! Moreover, a single definition can automatically align all subsequent references to it, by changing only a single line!

Also, I have programmed the 6502, although it was many years ago. I always used symbolic references when ever possible, and have done so ever since, in all the machine language programming that I have done. In fact, I have made efforts to augment and refine the usage of them greatly as I continue. Last year I wrote for the CDP1802, possibly the worlds most terrible to program processor. So having programmed in assembly is no excuse for not embracing symbolism and descriptive names.

Have you looked at bitRead() yet? Let's get back on track with the two issues I mentioned.

  1. writing a variable to an output port
  2. reading a single bit from a byte, to create that variable

I looked at the bitRead() instruction, but the definition/description doesn't say anything about using it to read an ASCII character.

Will bitRead("G",x) work in a for loop, to extract the bits from the ASCII character "G"? That's my next experiment.

Of course, your point about (I and 1) or (O and 0) being confused is valid, but I'm using a font where those characters are easily discerned. I learned to do THAT about 35 years ago... even when writing by hand, I ALWAYS slash my zeroes. :slight_smile:

BTW, when I wrote MOST of my 6502 programs back in the day... I used pencil and graph paper. (Yes, I'm totally serious.) :slight_smile: When I started to use MICROMON on the Commodore64, I thought it was great. :slight_smile:

The single biggest thing I miss in my (still NOOB) experience with the C++ language, is the easy ability to make and call subroutines. I have to un-learn about 35 years of experience... not an easy task. Baby steps! :slight_smile:

Willie...

Willie---:
The single biggest thing I miss in my (still NOOB) experience with the C++ language, is the easy ability to make and call subroutines.

It's just as easy in C++ as it is in Basic. Except they're called functions rather than subroutines, and they can be passed parameters and return a value, although they don't have to.
For example, a function that takes no parameters and doesn't return a value:-

void myFunction()
{
    // Do stuff here
}

"void" indicates that no value is returned, and the empty "()" brackets indicate that no parameters are passed to the function.

Then to call it from the 'loop()' function:-

void loop()
{
    myFunction();
}

No "return" is needed in this case, although it can be used. "return" is necessary if the function returns a value.
Rather than re-invent the wheel by explaining further here, you might find this useful:-
C++ Functions

The very first reply in this thread by OldSteve contains basically all you need.

Below is a modified version that you can basically drop in your program.

// word to display
char Good[] = "Good";

// datapins; least significant bit first
const byte datapins[] = {2, 3, 4, 5, 6, 7, 8, 9};

void setup()
{
  // initialize serial port for debugging
  Serial.begin(9600);

  // init the 8 data pins
  for (int cnt = 0; cnt < 8; cnt++)
  {
    // initial value low
    digitalWrite(datapins[cnt], LOW);
    // set as output
    pinMode(datapins[cnt], OUTPUT);
  }

  // for each character in the word Good
  for (int charcnt = 0; charcnt < strlen(Good); charcnt++)
  {
    // some debug output
    Serial.print("Character: "); Serial.println(Good[charcnt]);
    // set the bit values on the data pins
    for (int bitcnt = 0; bitcnt < 8; bitcnt++)
    {
      // value (0 or 1) using bitmask to check if a bit in the character is set or not
      bool bitvalue = (Good[charcnt] & (1 << bitcnt)) == (1 << bitcnt);
      // set corresponding output
      digitalWrite(datapins[bitcnt], bitvalue);
      // some debug output
      Serial.print("pin "); Serial.print(datapins[bitcnt]);
      Serial.print(", value: "); Serial.println(bitvalue);
    }
    // some debug output; separate each character
    Serial.println("======");
  }
}

void loop()
{

}

The use of the datapins array allows you to swap pins around if needed; e.g. if your PCB layout would require all kinds of difficult constructions to get the signals from the micro to the display, you can change the sequence.

The trick is the masking in the inner for-loop; understand that and you're good to go (after all, you're the one that has to maintain the code :smiley: ).

For demo purposes I have placed the basics in setup.

Willie---:
It's a step forward, in that I'm learning something new, and VERY different from the GWBASIC and 6502 machine language with which I am most familiar. :slight_smile:

Ah yes, I used to do a lot with the 6502. In assembler.

However, here you can just do a simple loop. A bit of bit-shifting, and iteration in a loop. Don't make things more complex than they need to be.

Whew! This is quite an education! :slight_smile:

Thank you, EVERYONE, for taking some time to share info with this NOOB! :wink:

This really is SO VERY DIFFERENT from what I am used to, I have to take some time to process it... I have to READ a LOT more, then think, and then try stuff! (I tend to learn best by doing... and looking at how others have done certain things, and copy/modify it for my purposes.)

I'm actually a bit lost on the serial commands, above... these displays are parallel. (Unless this is for me to open the TERMINAL in the IDE, to see it spit out some info?)

As for not making things too complex than they need to be... trust me, if I could just program this little guy in BASIC, I would already have it reciting Shakespeare!! :wink: LOL!

Willie---:
I'm actually a bit lost on the serial commands, above... these displays are parallel. (Unless this is for me to open the TERMINAL in the IDE, to see it spit out some info?)

Yes, they are parallel; both your display (the characters) and a terminal program (the bit pattern) will display data. You open e.g. the Serial Monitor in the IDE and you can see what the code spits out. I don't have your display so to check I just send it over the serial line to the Serial Monitor. I left it in because it can always be a useful debugging tool; once you're confident that the code is correct you can take the Serial.print and Serial.println statements out.