Convert seemingly ASCII code into integer

I am using the standard Adafruit BLE example to receive user-typed characters from a phone app via BLE UART. The incoming data is assigned to "ch" and it is readable by the serial-monitor on the COM port. I would like to be able to use the incoming data represented by "ch" as an integer, but it seems to be in ASCII format.

For example, if I type from the phone a "2" then "ch" value of "2" shows up in the serial-monitor via serial.write. But if I want to serial.print(ch) I get the corresponding ASCII value of 50 instead. How can I convert this ASCII reading into an integer with the value of 2?

Here is the simplified code:


  #include <Arduino.h>
  #include <bluefruit.h>

 void loop() {

 while ( bleuart.available() )   
  {
  uint32_t ch;
  ch = (uint32_t) bleuart.read();
 Serial.write(ch); Serial.print("       "); Serial.println(ch);
   }
}

RESULTS in serial-monitor: 2 50

============================================================
Then if I add print char(ch) to convert into an integer:


 Serial.write(ch); Serial.print("  "); Serial.println(ch);
 Serial.print(char(ch)); Serial.println(" <=  char(ch) ");

RESULTS in serial monitor:
2 50
2 <= char(ch)

10

<= char(ch)

===========================================================
Then I try to assign chr(ch) to an integer variable, called offTime2:


   Serial.write(ch); Serial.print("  "); Serial.println(ch);
   Serial.print(char(ch)); Serial.println(" <=  char(ch) ");
   offTime2=(char(ch));  
   Serial.print(offTime2);Serial.println("  <=  this is offTime2  ");

RESULTS in serial monitor:
2 50
2 <= char(ch)
50 <= this is offTime2

10

<= char(ch)
10 <= this is offTime2

Simple:

byte y = (ch & 0x0F); // y = 0x32 & 0x0F => 00110010 & 00001111 = 00000010 = 02 = 2
1 Like

Thank you very much! It worked! I will look up why it worked, unless there is a quick explanation.
Do you know why it prints another instance with a blank for ch value and a 10 for the y value?


    while ( bleuart.available() )   // RECEIVES DATA FROM OTHER REMOTE DEVICE VIA BLE
   {
   uint32_t ch;
   ch = (uint32_t) bleuart.read();

   Serial.write(ch);Serial.println(" <= this is ch value sent ");
   byte y = (ch & 0x0F); // y = 0x32 & 0x0F => 00110010 & 00001111 = 00000010 = 02 = 2  
   Serial.print(y);Serial.println("  <=  this is y  ");
  }

RESULTS:

3 <= this is ch value sent
3 <= this is y

<= this is ch value sent
10 <= this is y

Then I removed all the other prints and left only the print y:


  while ( bleuart.available() )   // RECEIVES DATA FROM OTHER REMOTE DEVICE VIA BLE
  {
   uint32_t ch;
   ch = (uint32_t) bleuart.read();

   Serial.write(ch);
   byte y = (ch & 0x0F); // y = 0x32 & 0x0F => 00110010 & 00001111 = 00000010 = 02 = 2  
   Serial.print(y);
   }

RESULTS:
33
10

Where 33 is the ch written (3) and y (3) and 10 just showed up.

If you expect some software routine to always convert valid characters to numerical values, you have to also screen those characters for invalid values, e.g. " " or "x" or "." or etc etc.

Golam gave you a method with no input validation.

You also need to decide what the software should do with invalid inputs.

1 Like

Thank you for the additional information. In this case the input range will be one integer from 2 to 30. The integer wil be used to represent minutes and compared to a running millis() timer calculation to turn power off when it reaches\exceeds y minutes running time. That part already works. So I can filter to look (accept) only those integers in the 2 to 30 range therefore ignoring any errors, misc. characters. I can also make the app-user only able to type and submit integers between 2 to 30.

Is there a general, fits-all way to do it? Or one that covers most situations? Thank you very much.

Not sure if the "atoi" (ascii to integer) macro suits your needs, but you may want to have a look at it.

void setup(){

  Serial.begin(9600);
   int val1;
   long val2;
   char str[] = "9893";
   char str2[] = "9893664";
   
   val1 = atoi(str); //ascii to integer

   Serial.print("String value1 as int = ");
   Serial.println(val1);

   val2 = atol(str2); //ascii to long

   Serial.print("String value2 as long = ");
   Serial.println(val2);
}

void loop() {}
1 Like

Thank you. I actually used ultoa before but did not think about it in this case. Will refresh on it and check. Appreciate it.

Also remember that the ASCII characters being sent are sent one at a time:
so, if you sent 27, it is going to come across as codes for '2' and '7', not the number 27. That is why the code works for single digits.

The atoi function is a great idea. But you will need to make sure that you collect all the digits coming in before running the macro on the collected ASCII codes.
Your input variable is only a single character. Not clear to me why you are using uint32_t rather than char type. ?required by the BLE

1 Like

Thank you. Valid points, and I am discovering the issues you mentioned now as we speak. I sent the "number" 32 and it only read 3...the first character.

The BLE coding behind the scenes is done by Adafruit and probably buried deep somewhere in their Arduino core files. Afraid that changing one thing there will mess up other things.

I will look up the ultoa (and itoa) solutions and digest it again. It's been a while back since I used them.

I had this for sending via BLE to phone. Will look into making it into receiving from BLE into Arduino:


       char charBuf[20];
      ultoa(duration, charBuf, 10);  // ultoa  vs itoa
       bleuart.write(charBuf);  // send to BLE

**

Thanks for the update, and happy coding

Except that it is deprecated.

What would you recommend to the OP in its place?

I use strtol() or strtoul():
https://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#gaf8ce3b8dae3d45c34c3b172de503f7b3

Thanks so much.
I have not had any trouble with atoi, and it still shows up in cppreference sites, and works well enough in my totally non-critical code. :grinning:

1. When we place 2 in the InputBox of the Serial Monitor (FIg-1) and then click on the Send button, the code 32 (00110010) called ASCII Code (Fig-2) travels towards UNO.


Figure-1:


Figure-2:

2. At UNO side, we catch this value and save in a char-type variable:

char ch = Serial.read();

3. In Step-2, ch-variable contains 00110010. To extract 2 (last 4-bit 0010), we need to remove/replace the upper 4-bit (0011) by 0s. This is done by doing bit-wise AND (&) operation. Because, the result is no more an ASCII code of a printable character (Fig-2), we save it in a byte-type variable y.

byte y = ch & 0b00000010;
==> y = 0b00110010 & 0b00000010;
==> y = 0b00000010;
==> y = 2; 

4. Why are you getting 10?
In Fig-1, there is a Line ending tab. If we select Newline option, then after sending ASCII code of 2, the Serial Interface will transmit the ASCII Code of Newline (0x0A = 10, Fig-2). Now, the UNO is receiving two data bytes: 0x32 and 0x0A which when printed in decimal base will appear as 50 (0x32 = 3x16 + 2x1 = 50) and 10.

5. @tomsz can upload the the following sketch to see the way of filtering out the non-printable Newline control character:

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  byte n = Serial.available();
  if (n != 0)
  {
    char ch = Serial.read();
    if (ch != '\n') //Newline charcater is filterd out
    {
      Serial.println(ch, DEC); //shows ASCII codes in decimal base
    }
  }
}

Output:

50   //ASCII code of 2 in decimal base

6. @tomsz can upload the the following sketch to see that the the non-printable Newline control character is not filtered out:

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  byte n = Serial.available();
  if (n != 0)
  {
    char ch = Serial.read();
    //if (ch != '\n') //Newline charcater is filterd out
    //{
      Serial.println(ch, DEC); //shows ASCII codes in decimal base
   // }
  }
}

Output:

50
10
2 Likes

The problem with atoi is that it can return 0 if the text is non-numeric; so you have to actually do a data validation before calling atoi. With strtol / strtoul you have a means to check.

1 Like

Amazing! I really appreciate your very nicely detailed help. I need some time to digest it and understand it, but the last part I understand now that a "newline" command (and similar seemingly trivial internal commands) do actually carry a code (ASCI in this case) with them. The rest, I will need to look at with a fresh mind. Thank you again! :slight_smile:

Welcome and have a fun in the Arduino World!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.