Reading Numbers From Serial
Reading Numbers From SerialIntroduction
Reading numbers from serial on an Arduino is needed surprisingly commonly. Exactly what is happening might be kind of hard to figure out.
Serial
Serial communication is digital, which means all data is transmitted in 1's and 0's. Typically, serial communication is done using ASCII letters. This means that, to send a number to the Arduino, the data sent is not the binary version of the number in base 2 (as an integer), but instead a sequence of characters for each digit in base 10 (which is human-readable). It is not an efficient use of bandwidth, but bandwidth is not usually a problem with Arduinos connect by USB.
Arduino Code
char commandLetter; // the delineator / command chooser char numStr[4]; // the number characters and null long speed; // the number stored as a long integer void serialEvent() { // Parse the string input once enough characters are available if(Serial.available() >= 4) { commandLetter = Serial.read(); //dump the buffer if the first character was not a letter if ( ! isalpha( commandLetter ) { // dump until a letter is found or nothing remains while(( ! isalpha( Serial.peak() ) && Serial.available()) { Serial.read(); // throw out the letter } //not enough letters left, quit if ( Serial.available() < 3 ) { return; } } // read the characters from the buffer into a character array for( int i = 0; i < 3; ++i ) { numStr[i] = Serial.read(); } //terminate the string with a null prevents atol reading too far numStr[i] = '\0'; //read character array until non-number, convert to long int speed = atol(numStr); Serial.println(speed); } }
Code Explanation
serialEvent
serialEvent is a function that is called by Arduino before each loop. So, you can separate out your serial handling from the rest of your code. You could also put the contents of this function in the loop() function and it would be similar.
Fixed-Width
Commands need to be delineated in some way. Fixed-width commands are convenient on Arduino because the Serial.available function tells you how many characters are waiting. It is also possible to delineate by special characters. An extremely common and simple version of this is a Comma Separated Value file. However, since it is simplest to use C strings, the character arrays which the serial is read into is necessarily fixed-width anyway. To get around this issue, a vector or linked list could be used, so the serial string can dynamically grow and shrink.
Command Differentiation
In many cases, a robot takes multiple kinds of inputs. For example, there might be multiple speeds, or a direction, or some kind of trigger. Prefixing each command with a unique letter can make it easy to differentiate what the following string is supposed to do.
C Strings
C strings are arrays of char data types. They must be terminated by a NULL character '\textbackslash 0'. Without the NULL character at the end, there is no way for any function to know how long the string is. For example, a print function would print the string and then a bunch of garbage characters until it gets to a NULL or the end of the memory available. In this case, it's less likely for that to happen since we are only using atol, which will only read until there is a non-numeric character. A non-numeric byte is more likely to randomly be at the end of the string than a NULL character.
Decimal | Character |
48 | '0' |
49 | '1' |
50 | '2' |
51 | '3' |
52 | '4' |
53 | '5' |
54 | '6' |
55 | '7' |
56 | '8' |
57 | '9' |
Char to Integer
Converting a single character to a number takes advantage of how characters are stored, which is typically in a format called ASCII. Think of a char of ASCII like an integer data type. Except that when printed, it is not a decimal number. The number in the int is translated into a letter on the screen according to a table. For example, the decimal number 97 is translated into the letter 'a'. Each letter is really stored as a 'number'. I'm showing it as a decimal (base 10) number, but it is really stored in binary (base 2). The C++ compile takes care of translating our base 10 decimal numbers into base 2 binary.
Here is how to take advantage of this:
int number = (int)charInput - (int)48;
// or:
int number = charInput - '0';
Since ASCII stores the letters sequentially, starting at 0, just subtract the value where 0 begins from each of the characters to convert it to its numeric form. This works regardless of the base of the character storage. So, it converts the ASCII character representing the number in base 10 into an integer in base 2.
Char Array (C-string) to Long Integer
Now, each of the letters from right to left need to be multiplied by a power of 10. Here is the basic idea of what atol does:
char charArray[5] = "5432"; // double quotes adds the \0 at the end long longnum = 0; //the converted number long exp = 1; //start with 10^0 int index = 0; //the current place in the array //find the end of the string - when character is not a number while ( isdigit( charArray[idx] ) ) { ++idx; } //begin base multiplication and number conversion while( idx >= 0 ) { //convert digit character to a long long currentDigit = (long) charArray[idx] - (long)48 currentDigit *= exponent; //number multiplied by 10^(len-idx) exponent *= 10; // next number to left is another power of ten longnum += currentDigit;
--idx; }