Read ASCII characters via I2C until Enter pressed

I'm trying to understand how this code works, obviously I don't right now, and how to modify it. I'm using a USB Host shield that converts a USB keyboard to ASCII characters. The code below is from the company I got the USB host from. It's a demo to prove that it works, it does. This code reads the characters I enter for 5 seconds then prints the number of characters and the characters in the Serial Monitor.

For the most part I understand the code but not a few of the commands they are using. I would like to modify this, or make a new sketch that will read the characters I type on the keyboard until I press the enter button. Then save those characters in a string. I want to be able to enter data like time, date, name, product ID and such.

A couple of questions. They use "Wire.write(1);" I assume the "1" is the parameter to read the number of characters. Then the "Wire.write(0);" the "0" reads the characters. Are there other parameters?

Thanks
John

/*
** Wire Master Reader of Hobbytronics USB Host Keyboard
** Created 24 Oct 2013
**
** This example code is in the public domain.
** www.hobbytronics.co.uk
*/

#include <Wire.h>

const int  adc_address=41;   // I2C Address

char keyboard_data[80];   // Array to store keyboard values   

void setup()
{
  //Send settings to ADC_I2C device (optional)
  Wire.begin();              // join i2c bus (address optional for master) 
  //Start Serial port
  Serial.begin(9600);        // start serial for output
}

void loop()
{
  unsigned char i;
  char keyboard_chars;

  Wire.beginTransmission(adc_address); // transmit to device
  Wire.write(1);                       // ask how many characters are in buffer
  Wire.endTransmission();              // stop transmitting  
  Wire.requestFrom(adc_address, 1);    // request 1 bytes from slave device
  while(Wire.available())
  {
     keyboard_chars = Wire.read();
  }  

  Serial.println(keyboard_chars, DEC); // print number of buffer characters
  
  Wire.beginTransmission(adc_address); // transmit to device
  Wire.write(0);                       // get keyboard characters
  Wire.endTransmission();              // stop transmitting  
  Wire.requestFrom(adc_address, keyboard_chars);    // request characters from slave device
  i=0;
  while(Wire.available())
  {
     keyboard_data[i++] = Wire.read();
     keyboard_data[i] = '\0';
  }  

  Serial.println(keyboard_data);       // print the character  
  keyboard_data[0]='\0';

  delay(5000);                         // Wait 5 seconds
}

Stumpy_L:
Are there other parameters?

That's defined by whatever is at the other end of the I2C bus -- i.e. your USB Host shield.

That makes sense, it would be dependent on what your talking to, it's their parameters.

Thanks

The sketch that you have posted is fully understandable by applying the principles of the I2C communication protocol.

1. The Keypad has been connected with the UNO using I2C Bus; the I2C bus address for the keypad is 41 decimal.

2. The Keypad logic maintains a FIFO Buffer to accumulate the ASCII codes of the pressed down keys. The number as to how many keys the user has pressed is stored in a register with internal address: 1 (0x01). Let us call it counterRegister The FIFO Buffer is located at the internal address: 0 (0x00).

3. Let us study the meanings (see the comments against the code lines) of every line of your posted sketch (I have modified the sketch a bit for my own understanding/comfort without undermine the functionality of the Keypad).

#include <Wire.h>  //nneded for the operation of I2C Bus and functions/methods

const int  adc_address=41;   // I2C Address of Keypad

char keyboard_data[80];   // Array to store keyboard values -- ASCII codes of the pressed down keys   

void setup()
{
  Wire.begin();              // create I2C Bus -- join i2c bus (address optional for master)
  Serial.begin(9600);        // start serial for output
}

void loop()
{
  unsigned char i;
  char keyboard_chars;  //holds a number as to how many keys have pressed down in the Keypad.

  Wire.beginTransmission(adc_address);     // checking presence of Keypad -- transmit address to device
  Wire.write(1);                       // pointing CounterRegister that hold how many keys have been pressed
  Wire.endTransmission();        // transfer the queued data and then stop transmitting 
  //---------------------------------------------------------------------------------------------
  Wire.requestFrom(adc_address, 1); // request 1 byte from slave which holds content of counterRegister
  keyboard_chars = Wire.read();  //keyboard_chars holds number as to how many keys have pressed
 

  Serial.println(keyboard_chars, DEC); // print number of buffer characters
 
  Wire.beginTransmission(adc_address); // roll calling slave device by its address
  Wire.write(0);        //pointing FIFO Buffer that hold the ASCII codes of the pressed down keys
  Wire.endTransmission();              // stop transmitting
  //-------------------------------------------------------------------------------------------------- 
  byte n = Wire.requestFrom(adc_address, keyboard_chars); //request ASCII codes of pressed keys
  for(int i=0; i<n; i++) // n is expected to be equal to keyboard_chars
  {
     keyboard_data[i] = Wire.read();  //storing ASCII codes of pressed down keys into array
  }
  keyboard_data[n] = '\0';   //insert null byte in the array as last character 

  Serial.println(keyboard_data);       // print the characters that have been entered from the Keypad 
  delay(5000);                         // Wait 5 seconds
}

That's terrific. It's basically what I thought was going on but there were a couple of unknowns for me in there. You've provided a great explanation.

So back to my other question, how do I have it continue to enter characters into the buffer until I press the "Enter" key?

Obviously I would remove the 5 second time delay, Can I test, (using a while loop?) to stop when it sees "Enter"? Or do I read the buffer until empty and don't include the enter as part of the string?

Thanks again
John

@Robin2's Serial Input Tutorial would probably be applicable to your application. You could adapt the techniques to the way your USB shield works.

BTW, why not post a link to this shield's datasheet?

Here's a link to the host chip I bought, I don't see a datasheet but you may. You can order the chip pre- programmed for a number of functions. You can see links for the different ones. This is the second one I've gotten. The first was for the joystick. you used to be able to buy them individually all assembled, they are having some internal issue right now so you can only or the kit with a minimum of 10. I bought the chip, they have two schematics, I went with the basic one, which they don't seem to have today, same schematic they have but no eeprom. I can send you the other schematic if you're interested. Got the components and it worked first time. They are a great product and time saver.

Stumpy_L:
Here's a link to the host chip I bought, I don't see a datasheet but you may.

OK, it's a USB-equipped PIC with proprietary firmware.

FWIW, if this were my project, I'd start out by implementing it using the Serial interface option. That's less complex than I2C and @Robin2's Tutorial (that I linked above) would be directly applicable.

Stumpy_L:
So back to my other question, how do I have it continue to enter characters into the buffer until I press the "Enter" key?

I assume that you want to state that Arduino (UNO) will start reading the ASCII codes of your pressed down keys as they are being typed/entered until 'Enter' Key is detected. Try these codes:

#include<Wire.h>
void setup()
{
    Serial.begin(115200);
    Wire.begin();
    Wire.setClock(400000);    //fast mode
}

void loop()
{
    Wire.beginTransmission(41);
    Wire.write(1);   //pointing counterRegister
    Wire.endTransmission()    

    byte n = Wire.requestFrom(41, 1);
    if (n != 0)   //at least one key has been pressed
    {
       Wire.beginTransmission(41);
       Wire.write(0);   //pointing Buffer
       Wire.endTransmission()

       Wire.requestFrom(41, 1);
       char m = Wire.read();
       if(m != '\r')       //Carriage return
       {
           myChar[i] = m;   //save in char myChar[20] array
           Serial.print(m);   //show character on Serial Monitor
           i++;
       }
       else
       {
           //process received data
       }        
    }
}

Yes that is what I want and your code does look like it will work, not that I'm doubting it would.

I wont get to try this until tomorrow night but will let you know as soon as I do.

Thanks so much for the help, always get great help on the forum
John

OK, ran this last night and it works fine but need to go the next step. Also a bit frustrated today but more on that in a bit. Your code works great, I just had to declare a couple of variables to compile it. I type in what I want, shows on serial monitor, press enter I get a new line. I tried setting what I typed into a second variable, had no luck. Declared the variable as both a string and a char array. Tried putting that logic in a couple of places, inside the if or the else, outside of both. The other thing that was odd was if I put something in the else, where you have "process received data" even just a simple Serial.print statement, then when I typed something it didn't show.

My frustration is I had done another project using Robin2's tutorial above to concatenate 3 integers, send them to a second Arduino then parse them out and display them. So I went to open those projects to review how I did that and got an error that it failed to open the sketch. I then noticed in Windows Explorer that some of the folders that hold my projects the folder color was a bit shaded, yet others were not. I can open those not shaded but not the shaded ones. But this is an issue for a different post.

Back to this, here's what I would like to do and need help with. First I would like to make this sketch a function so I can call it multiple times for different variables. Say I call it KeyboardRead, because its returning a value I know I don't declare it as a "void" would it be a "char" or a "String" (char KeyboardRead()). I have a TFT display with touch, I will have a couple of fields to fill in, date, product ID and operator. So if I press the date field I would call this function by "date = KeyboardRead()" I type in "10/4/09" press enter and now that is in date. Same for the other variables. Am I some what right so far?

How do I do that? Here is your sketch I was using last night.

// read keyboard until enter 

char myChar[80];
int i = 0;

#include<Wire.h>

void setup()
{
    Serial.begin(115200);
    Wire.begin();
    Wire.setClock(400000);    //fast mode
}

void loop()
{
    Wire.beginTransmission(41);
    Wire.write(1);   //pointing counterRegister
    Wire.endTransmission();    

    byte n = Wire.requestFrom(41, 1);
    if (n != 0)   //at least one key has been pressed
    {
       Wire.beginTransmission(41);
       Wire.write(0);   //pointing Buffer
       Wire.endTransmission();

       Wire.requestFrom(41, 1);
       char m = Wire.read();
       if(m != '\r')       //Carriage return
       {
           myChar[i] = m;   //save in char myChar[20] array
           Serial.print(m);   //show character on Serial Monitor
           i++;
        } // end if(m != '\r')
       
       else
       {
           //process received data
          
       } // end else if    

          
    } // end if (n != 0)

         
} // end void loop

Thanks again for your help and your time
John

What I understand from your post is that you want connect a 101-key based PC Keyboard with Arduino UNO through a smart interface, and you have been able to do it. Now you can catch, read, and display any character whatever you enter from the Keyboard. If this is the case, then you can develop any application whatever you like based on the ASCII code of the pressed down key (s) provided you follow the SSS Strategy. Small Start Strategy is a development methodology in which you to begin with a basic (simple and elementary hardware + software) thing, make it work; add another incremental amount; make it work and continue like this until the whole project is completed.

GolamMostafa, you are correct in that is what I what to do and I always use the SSS, although I have never heard that term but I like it. I have been programming industrial machines for over 40 years, mostly with PLC's. The Arduino stuff is just a hobby, I'm a typical geek who doesn't play golf but sits in the spare bedroom trying to figure these things out. With help from people like you I have been able to. I build my programs one step at a time. I develop one part separately, then another, once I have all the parts working I put them together in a single sketch.

For this project I needed a display, so I worked on learning how to program a TFT display with touch, I have so now I put that aside. I also need to store real time data on a SD memory card, I ordered a shield the other day, I will use the tutorial that comes with it to learn that and put that aside.

Now what I am trying to do, and as you mention, have been able to connect a USB keyboard to a Mega and I can read the ASCII codes on the Serial monitor using the sketch that you provided me the other day. Now I am trying to understand how to take those ASCII characters and put them together in a single variable. Your sketch gets me the characters, how do I use that so the value in my variable "date" will be "10/4/19"

Please understand I am not asking you to do my project for me, I am asking you to educate me, help me understand so I can do this and future projects on my own.

I appreciate your help
Thanks
John

This is your first mention that you’re using a Mega. The Mega has multiple Hardware Serial ports. So, I’ll repeat the advice that I gave you above: Use the serial interface mode of your USB Host Shield instead of I2C.

For the sake of making things easier on Newbies, the TwoWire class (of which Wire is an object) tries really hard to provide an interface with the same look and feel as other classes that inherit from the Stream class. But, it’s a force fit at best. But, due to the master / slave relationship (among other things), I2C is not really a streaming interface. A keyboard, however, is a streaming device -- Admittedly, it’s slow and unidirectional. But streaming nonetheless.

If you use one of the Mega’s Hardware Serial ports instead, it will handle the reception and buffering of the incoming bytes for you, automatically via interrupts. Your job will then simply be pulling from the interface’s buffer into your own byte array, watching for the correct delimiters, and then parsing the input and taking appropriate action.

You won't need to mess around with Wire.beginTransmission, Wire.write, Wire.requestFrom, etc.

gfvalvo, I didn't bother mentioning it was a Mega as I didn't think it really matter as far as the logic. I have used multiple serial ports with the Mega on other projects, mainly why I use the Mega, other than more I/O points, but I can leave Serial 0 for the serial monitor and use the other ports for interface, like Xbees.

I can use serial with the Host Shield and will give it a try, but as a Newbie can you explain why that would make a difference? I do read the characters using I2C, is it because as you mentioned there is better buffering on the serial port than I2C? What I'm trying to understand now is how to parse that data into my variable. As mentioned for whatever reason I can't open my other project where I did that but I got the logic from the link you sent so I'll just go back to that.

Thanks for your reponse
John

As I mentioned, a Serial interface is more compatible than I2C with the asynchronous streaming nature of a keyboard. Look how much more straight-forward the vendor's example code for Serial is verses I2C: USB Host - Keyboard to ASCII Converter.

As I also mentioned in Reply #7, using Serial makes the techniques in @Robin2's tutorial directly applicable with no modifications for the special needs of I2C. So, you just need to go back and study that tutorial again to re-create the code from your other project that seems to have a file corruption problem.

OK, will do, I'll work on that this weekend and let you know how I make out.

Thanks
John

You decide first which protocol you are going to use to acquire characters from the Keyboard:

UART
I2C

I have the I2C working and I will try the Serial mode this weekend. I'll try to use Robin's method to parse the characters into a variable, which I have used in another projects, from both methods and see where it goes from there

Thanks both for your help and interest.
John

I reconnected the USB host to serial port 1 on the Mega and as you both said it worked fine, exactly what I need for my project. As a mind exercise I will see if I can use this parsing method when I read the keyboard via I2C, but at least now I can continue with my project.

I do have one more question. For connections between the host and Mega I just have 5V, GND and TX. The info on the website says I only need RX if I want to change the baud rate, it defaults to 9600 so that's fine. The directions also say the "SS pin goes high when Key pressed, low when released" but I don't see where I would connect the SS pin to the Mega. Is that just so that if I wanted to know that a key has been pressed I could connect it to a digital input and have some logic to monitor it?

Thanks for all your help and patience with me
John