Analog pin reading 0-1023 but Characteristic.writeValue writes 0-255. Why?

Hello all,
I am measuring 0-3.3V on analogPin(A0) with a variable power supply and I am averaging the read value. A Serial.print of the 'Characteristic.writeValue' displays the expected 0-1023 values in the Serial Monitor but when I check it on my iPhone with either the nRF Connect or the LightBlue apps it instead is reading the 0-255 value(in HEX) until it reaches around 1V, then it will start again at 0-255 and keeps repeating 0-255 until I stop at 3.3V. I was able to actually match a 0-255 reading from 0-3.3V on both the Serial Monitor as well as the nRF Connect app by using the 'map(val, 0, 1023, 0, 255); ' math. But I want the better resolution of 0-1023 at the iOS end. So it seems the issue is in the 'BLECharacteristic.writeValue? I am very thankful for any pointers. Here is all of the code and thanks all.

[code]
/*
  A sketch to test the ability to read one analog sensor pin and report its 0-1023 value to the nRF or
  LightBlue app on an iPhone via a custom service and a custom characteristic.  Ultimately I will need to
  have six analog pins writing to six different characteristics under one service with the goal of displaying those 0-1023
  values in UIText fields on an iOS app.
  I am using the BLE radio on an Arduino MKR-1010 Wifi board.
*/

#include <ArduinoBLE.h>

// BLE UUIDs
#define BLE_UUID_SENSOR_SERVICE           "9c6d55da-8908-11ec-a8a3-0242ac120002"  //custom 128=bit UUID
#define BLE_UUID_ANALOG_SENSOR            "5c23e6fa-8909-11ec-a8a3-0242ac120002"  //custom 128-bit UUID 

// BLE
#define BLE_DEVICE_NAME                   "Sensor test"
#define BLE_LOCAL_NAME                    "Sensor test"

BLEService sensorService( BLE_UUID_SENSOR_SERVICE );
BLEIntCharacteristic sensorReadCharacteristic( BLE_UUID_ANALOG_SENSOR, BLERead | BLENotify );

#define BLE_LED_PIN                       LED_BUILTIN
#define SENSOR_PIN                        A0

const int numReadings = 10;

int readings[numReadings];      // the readings from the analog input
int readIndex = 0;              // the index of the current reading
int total = 0;                  // the running total
int average = 0;                // the average

void setup() {
  Serial.begin(9600);    // initialize serial communication
  while (!Serial);

  Serial.println( "One Sensor Test" );

  pinMode( BLE_LED_PIN, OUTPUT ); // initialize the built-in LED pin to indicate when a central is connected
  pinMode( SENSOR_PIN, INPUT );
  analogReadResolution( 10 );

  // begin initialization
  if ( !setupBleMode() )
  {
    Serial.println( "Failed to initialize BLE!" );
    while ( 1 );
  }
  else
  {
    Serial.println( "BLE initialized. Waiting for central to connect." );
  }

  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
}
void loop()
{
  bleTask();
  SensorTask();
  
}

void SensorTask()
{
  // subtract the last reading:
  total = total - readings[readIndex];
  // read from the sensor:
  readings[readIndex] = analogRead(SENSOR_PIN);
  // add the reading to the total:
  total = total + readings[readIndex];
  // advance to the next position in the array:
  readIndex = readIndex + 1;

  // if we're at the end of the array...
  if (readIndex >= numReadings) {
    // ...wrap around to the beginning:
    readIndex = 0;
  }

  // calculate the average:
  average = total / numReadings;
  // send it to the computer as ASCII digits
  delay(10);        // delay in between reads for stability

}

bool setupBleMode()
{
  if ( !BLE.begin() )
  {
    return false;
  }

  // set advertised local name and servive UUID
  BLE.setDeviceName( BLE_DEVICE_NAME );
  BLE.setLocalName( BLE_LOCAL_NAME );
  BLE.setAdvertisedService( sensorService );

  // BLE add characteristics
  sensorService.addCharacteristic( sensorReadCharacteristic );

  //add sevice
  BLE.addService ( sensorService );

  //set the inbitial value for the characteristic
  sensorReadCharacteristic.writeValue( average );


  // set BLE event handlers
  BLE.setEventHandler( BLEConnected, blePeripheralConnectHandler );
  BLE.setEventHandler( BLEDisconnected, blePeripheralDisconnectHandler );

  // start advertising
  BLE.advertise();

  return true;
}

void bleTask()
{
  BLE.poll();

  sensorReadCharacteristic.writeValue( average );
  Serial.print( "Read Average: ");
  Serial.println( average );   // serial monitor reports 'average' values as 0-1023
  delay (500);

}

void blePeripheralConnectHandler( BLEDevice central )
{
  digitalWrite( BLE_LED_PIN, HIGH );
  Serial.print( "Connected to central: " );
  Serial.println( central.address() );
}

void blePeripheralDisconnectHandler( BLEDevice central )
{
  digitalWrite( BLE_LED_PIN, LOW );
  Serial.print( "Disconnected from central: ");
  Serial.println( central.address() );
}
[/code]

I don't know anything about the library you're using, but 0-255 is one byte (8-bits) and the analogRead() value of (0-1023) is 10-bits of a 16-bit integer. The 6 most significant bits are lost like this:

I'll show 16-bits in groups of 4 to make it readable (1).
254 decimal = 0000 0000 1111 1110 binary
255 decimal = 0000 0000 1111 1111 binary
256 decimal = 0000 0001 0000 0000 binary

If you're only reading the 8 least significant bits it "loops back" to zero.

If the library has a way of transmitting/receiving a 16-bit integer that will solve your problem.

If not, you can bit-shift by 8 bits to read & send the next 8 bits. Then after transmitting/receiving you shift-back the other way (into a 16-intager) and you can combine the high & low bytes with bitwise OR.

(1) It's fairly common add a space between every 4-bits (for humans, not in programs) because it's easier to read and each group of 4-bits converts exactly to a hexadecimal value. That means you can easily learn to convert any size numbers between binary & hex in your head! (It's not so easy converting to decimal.)

For example, 11 binary = 3 in decimal or hex, so...
0011 0011 binary = 33 hex (easy!)
0011 0011 binary = 51 decimal (I used a calculator)

1 Like

I can't confirm what you are finding. When I run your code on a Nano33 BLE and jumper A0 to 3.3v I see values like FD 03 00 00 in nrf connect, and with light blue and data format set for unsigned little endian I can see values like 1018, 1017, 1019, etc.

The characteristic is sending the 4 bytes of the integer. I have checked this with sending a large value.

I am using LighBlue and nrfConnect on an Android phone.

1 Like

Cool! @DVDdoug! Thanks for the lesson.. that makes more sense now :slight_smile:

Hey @cattledog , really?? Umm, that's interesting. So in nRF you are receiving 0xFD 03 00 00? And also I can't seem to find the unsigned little endian setting for Characteristic in LightBlue on the iPhone app. Very strange but in a way makes me feel better? Now to figure out how you are seeing exactly what I was expecting to see but I'm not. lol. Okay well armed with this knowledge I will get back to the bench and see whats happening with my phone.
Thanks!

In the Android version, when you go to the 5c23... characteristic at the bottom of the Properties section is a box with Data Format. I changed it from Hex to unsigned little endian.

Wow! That's cool looking. Yeah the iOS version looks nothing like that but I'll check it again in the morning when I can get to my bench.

Thanks @cattledog.. I needed an update on the Lightblue app and now is reading correctly.. could have sworn I was current! I'll have to study @DVDdoug's post a bunch more so I can try to understand the HEX value's in nRF. Now I need to figure out the best way to send all six sensor readings at the same time.

What are the six sensor readings. Are they integers, floats, text values? You may be best off using six different characteristics rather than one characteristic containing all the data which you parse at the receiving end.

Are you planning to use Light Blue to read the different sensors on a phone?

Can you explain more about the bigger picture?

I can't go into too much detail here but my idea would require up to six sensors that will all be providing the standard analog read Int (0-1023). I already have a basic one sensor iOS app that I co-wrote with another more experienced coder that will need to be updated to read and display the additional five sensors. But I did that with an AdafruitBLE board that was just running in DATA_MODE so it wasn't communicating with a proper BLE Service/Characteristic communication that I will definitely need to do now with this expanded idea. I was wondering which would be better: Sending one characteristic with Flags to send the six different values and have that parsed out on the iOS side OR go with the six characteristics which is how I would prefer to do it. I already have a rough sketch that is setup with (1) Custom Service and (6) Custom Characteristics but I wanted to make sure one sensor would work correctly first! lol Once this coding is done I can move onto the hardware side of things.

Yes, that is how I would do it.

1 Like

Hi all,

I'm having a very similar issue to @kwburhans (I even tried modifying my code to reflect his use of BLE UUIDs and Service and Characteristic designs, but it didn't fix my problem). I too am trying to read analog voltage from a sensor on pin A6 of my MKR Wifi 1010 board using BLE and the app LightBlue. Similarly, I am getting values around 200 in the Unsigned Little-Endian data format, but when I run my analog read code and print the "voltage" float in the serial monitor I'm getting values of ~980 mV. I know I'm getting data from that pin because I used the subscribe function and watched values go from 200 to 0 when I disconnected my sensor from A6. I guess I'm just not understanding what this 200 value is, or how to convert it to voltage. I just downloaded the LightBlue app for Android yesterday, so I'm assuming @cattledog 's suggestion to update won't help me here.

Code:

//Sketch trying to read analog voltage data to BLE app LightBlue from a methane gas sensor on pin A6 on MKR Wifi 1010 board stacked with GPS shield and ENV shield (A6 not utilized by any of the shields)
#include <ArduinoBLE.h>
BLEService Service("180A"); // creating the service "Device Information"

BLEUnsignedCharCharacteristic MethaneVoltage("2A58", BLERead | BLENotify); // creating "Analog" characterisitc, alternatively Methane Concentration characteristic is 2BD1

void setup() {
  Serial.begin(9600);    // initialize serial communication
  //initialize ArduinoBLE library
  if (!BLE.begin()) {
    Serial.println("starting Bluetooth® Low Energy failed!");
    while (1);
  }

  BLE.setLocalName("MKR WiFi 1010"); //Setting a name that will appear when scanning for Bluetooth® devices
  BLE.setAdvertisedService(Service);

   //add characteristics to a service
  Service.addCharacteristic(MethaneVoltage);

  BLE.addService(Service); 
  MethaneVoltage.writeValue(0); //initial value of characterisitc 
   BLE.advertise(); //start advertising the service
}

void loop() {
  
  BLEDevice central = BLE.central(); // wait for a Bluetooth® Low Energy central

  if (central) {  // if a central is connected to the peripheral
    Serial.print("Connected to central: ");
    
    Serial.println(central.address()); // print the central's BT address

    while (central.connected()) { // while the central is connected:
     int MethaneValue = analogRead(A6);//Do I need to convert the straight analog read to voltage? (val/1024.0)*reference where reference = 5 
        MethaneVoltage.writeValue(MethaneValue);
        delay(2000); // post every 2 seconds to LightBlue
    }
  }
}

int MethaneValue = analogRead(A6);
MethaneVoltage.writeValue(MethaneValue);

BLEUnsignedCharCharacteristic MethaneVoltage("2A58", BLERead | BLENotify);

To send the analogRead() value you should change the characterisitc to the BLEIntCharacteristic like in the previous postings in the thread.

An unsignedChar data type can only hold values 0-255.

Thank you , @cattledog for catching that. I applied the change to the characteristic data type and I am getting values of ~140-150. I did the conversion from the pin reading to actual voltage, ie. (analoginput/ 1023)*voltage reference. In this case I made the reference 5000 so that the ending result is in millivolts. This math checks out, as I'm getting around 720 mV in my serial monitor running my non-BLE code.
However , when I made the changes to convert to voltage in my code, I'm now getting all 0's in the LightBlue app. Any idea why?

//Sketch trying to read analog voltage data to BLE app LightBlue from a methane gas sensor on pin A6 on MKR Wifi 1010 board stacked with GPS shield and ENV shield (A6 not utilized by any of the shields)
float reference= 5000;
#include <ArduinoBLE.h>
BLEService Service("180A"); // creating the service "Device Information"

BLEIntCharacteristic MethaneVoltage("2A58", BLERead | BLENotify); // creating "Analog" characterisitc, alternatively Methane Concentration characteristic is 2BD1

void setup() {
  Serial.begin(9600);    // initialize serial communication
  //initialize ArduinoBLE library
  if (!BLE.begin()) {
    Serial.println("starting Bluetooth® Low Energy failed!");
    while (1);
  }

  BLE.setLocalName("MKR WiFi 1010"); //Setting a name that will appear when scanning for Bluetooth® devices
  BLE.setAdvertisedService(Service);

   //add characteristics to a service
  Service.addCharacteristic(MethaneVoltage);

  BLE.addService(Service); 
  MethaneVoltage.writeValue(0); //initial value of characterisitc 
   BLE.advertise(); //start advertising the service
}

void loop() {
  
  BLEDevice central = BLE.central(); // wait for a Bluetooth® Low Energy central

  if (central) {  // if a central is connected to the peripheral
    Serial.print("Connected to central: ");
    
    Serial.println(central.address()); // print the central's BT address

    while (central.connected()) { // while the central is connected:
     int analogval= analogRead(A6);
     int MethaneValue= (analogval/1023)*reference;//Do I need to convert the straight analog read to voltage? (val/1023.0)*reference where reference = 5 
        MethaneVoltage.writeValue(MethaneValue);
        delay(2000); // post every 2 seconds to LightBlue
    }
  }
}


If you are trying to send a milli volt value there are two issues that I see.

The Nano BLE is not 5v tolerant, and the analog input should be limited to 3.3v. Your mv reference for full scale will be 3300.

There is going to be integer truncation when you do this math and the value winds up as zero.

int MethaneValue= (analogval/1023)*reference;

Try instead

float MethaneValue = (analogval)(3300.0/1023.0); //calculate as float
MethaneVoltage.writeValue((int)MethaneValue); //cast to integer for BLE characteristic

Blockquote The Nano BLE is not 5v tolerant, and the analog input should be limited to 3.3v. Your mv reference for full scale will be 3300.

Usually, yes but I'm using a power booster to be able to use a 5v sensor with a MKR Wifi 1010 board, so my voltage range is still 0-5.

The bit of code you included at the end, with the edit to 5000.0 rather than 3300.0 was what I needed! I'm now getting mV voltages to my LightBlue app that match the voltage in the serial monitor.
It might be eventually useful to be able to include the decimal points, which I believe would mean converting the float data to a string and then casting it that way? But

  1. converting a float to a string on a MKR board seems difficult because you can't use the dtostrf() function. It looks like I may be able to use a buffer to write a float?
  2. I've assigned the characteristic as an integer. I've looked over the BLEcharacteristic page and it seems like I can make it a float characteristic or a string value?

This is one of my first Arduino projects and the first with BLE, I've never used strings before so I'm not confident but I think I'm on the right track?

Thank you!

As long as the sensor never returns a voltage greater than 3.3 you will be OK. Can you guarantee that?

I've looked over the BLEcharacteristic page and it seems like I can make it a float characteristic or a string value?

Yes, you can do either. It all depends on how you are going to use the value you get. Reading the 4 bytes of an integer which represents 100x or 1000x 0r 10000x a floating point value is quite common. You could make the value a 2 byte integer instead of a 4 byte integer if you want to make things slightly more efficient. But given the MKR WiFi 1010 and the phone there's really no need.

Sending the mv value seems appropriate given you are using the 10 bit analogRead(). I think you can get a 12 bit adc reading on the MKR 1010. How fine do you need to slice the baloney :wink:

As long as the sensor never returns a voltage greater than 3.3 you will be OK. Can you guarantee that?

I'm using a voltage divider with resistors to ensure it!

Honestly, the majority of your second paragraph is beyond my current understanding, but I think I agree that the difference between 744 and 744.23 mV should not greatly alter what I need the analog voltage for. The decimals still jump a little even when getting "stable" readings so it can probably just be considered noise.

Thanks for your help!

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