How to read Serially Broadcasted Bluetooth data, from Arduino to PC?

Hi All,

I have successfully connected an Arduino to an RC car, and I can get the PWM (that is sent to the RC car motor) displayed in the serial monitor.

I’ve connected an XM-15B bluetooth module as per this site

I’ve opened up another Arduino IDE on the PC, and am selecting one of the two new COM ports that have shown up, after I’ve set up the Bluetooth module. I just chose any Arduino board, but when I run the serial monitor, nothing shows up. I’m not sure what i’m missing.

What i’m trying to do broadcast the recorded PWM from the RC car, and then read and record it on a PC. I figure if I can just get it to appear in a serial monitor, then I can copy and paste it into a text file. Can arduino create text logs automatically?

I used basic bluetooth code to test the module, so i know its working, and i set its baud to 115200, as well as the serial monitor, and Windows 7 settings.

Here’s the code i’m using:

#include <SoftwareSerial.h>

SoftwareSerial Bluetooth(10, 11); // (RX, TX)

volatile int pwm_value = 0;
volatile int prev_time = 0;
 
void setup() {
  Serial.begin(115200);
  pinMode(9, OUTPUT);  // this pin will pull the XM-15B CE pin HIGH to enable the module
  digitalWrite(9, HIGH);
  Serial.println("AT Command:");
  Bluetooth.begin(115200);  // XM-15B default speed
  // when pin D2 goes high, call the rising function
  attachInterrupt(0, rising, RISING);
}
 
void loop() { 
   // read from XM-15B ==> Serial Monitor
  if (Bluetooth.available())
  Serial.write(Bluetooth.read());

  // read from Serial Monitor ==> XM-15B
  if (Serial.available())
  Bluetooth.write(Serial.read());
  delay(20);
  }
 
void rising() {
  attachInterrupt(0, falling, FALLING);
  prev_time = micros();
}
 
void falling() {
  attachInterrupt(0, rising, RISING);
  pwm_value = micros()-prev_time;
  Serial.println(pwm_value);
}

Any help would be greatly appreciated!

I’m guessing I might have to have another arduino on the PC end, and another bluetooth module to receive? But my laptop has bluetooth built-in so i don’t think I need a second arduino/bluetooth pair.

exwhyzed:
my laptop has bluetooth built-in so i don’t think I need a second arduino/bluetooth pair.

You’re dead right about that. What you have to do is forget about the serial monitor and send the data to a proper terminal programme like Realterm. This can record the data to a CSV file directly - complete with timestamping off the PC clock. It’s that simple. RealTerm is a freebie, there are several others.

I just chose any Arduino board, but when I run the serial monitor, nothing shows up. I’m not sure what i’m missing.

One possible reason for that is that you are just choosing any board. I assume you know what board you are using, and it would make some sense to nominate it. A more likely reason is that you are using software serial. This is never a good idea, there is no evidence that you have a good reason for doing it, and I bet you don’t, and you are asking for all the grief you are getting if you use it at 115200. This is assuming you are actually using 115400. This line

Bluetooth.begin(115200); // XM-15B default speed

suggests the code is nonsense written by an idiot - I presume it’s not you.

Hi Nick_Pryner,

I'm going to try to open up a session in a Terminal Program, like Realterm, and see if I can get a reading. This is the type of thing i wanted to do in the first place, thanks for the suggestion!

Yes you are correct that I am using software serial, and I no idea why i'm using it. I just basically mashed two code bits together, thinking that it would work, since each code bit worked on its own.

I'm going to report back after tinkering with this project for a while longer. Thanks!

OK, if you are still starting off, you might find the following background notes useful

http://homepages.ihug.com.au/~npyner/Arduino/GUIDE_2BT.pdf http://homepages.ihug.com.au/~npyner/Arduino/BT_2_WAY.ino

Note that, while your IDE may be connected on COM9 or the like, don't be surprised if RealTerm connects on something like COM40

Hey thanks for the links! I'm currently reading the guide and will try the 2 way BT example later on.

Things seem to be working now all of a sudden. I think my error was powering the bluetooth module from a non arduino source, but connecting the signal pins to the arduino. I'm not sure if i connected the Arduino GND with the other 5V gnd. Anyhow, I was getting gibberish in the serial monitor (usually caused by mismatched baud rates) and squares in the terminal program (which I haven't seen before).

Anyhow, plugging the bluetooth into an arduino uno only seemed to solve the problem.

My project is now off to a great start, thanks for you help Nick_Pryner!

Ok i’ve run into a new problem which has stumped me:

What worked:
Using the below code

volatile int pwm_value = 0;
volatile int prev_time = 0;
 
void setup() {
  Serial.begin(9600);
  pinMode(9, OUTPUT);  // this pin will pull the XM-15B CE pin HIGH to 

enable the module
  digitalWrite(9, HIGH);
  // when pin D2 goes high, call the rising function
  attachInterrupt(0, rising, RISING);
}
 
void loop() { 
  delay(20);
  }
 
void rising() {
  attachInterrupt(0, falling, FALLING);
  prev_time = micros();
}
 
void falling() {
  attachInterrupt(0, rising, RISING);
  pwm_value = micros()-prev_time;
  Serial.println(pwm_value);
}

I connected the RC receiver’s output to Arduino UNO hardware interrupt 2, and was able to view the PWM in the Serial monitor. I was also able to read the PWM using the new com port of the bluetooth module that was broadcasting the UNO’s serial data.

What stumped me:
Now I’m trying to read 4 outputs from the RC receiver. First problem is

that the UNO only has two hardware interrupts. I had a Leonardo Pro micro lying around, which looks like it has 5 hardware interrupts. But in looking online, it seems that two of those interrupts are tied up in the RX and TX pins, which i need for the bluetooth module, so that scraps that idea.

Next solution is to use Pin Change Interrupts.
I used the below code and it worked to read one RC signal. But i’m not sure how to modify it to read and print 4 RC signals.

I’ve used code before that prints data of two joysticks on the serial monitor beside each other, but in that case, each type of data had a name like JoystickLeft_Xaxis, JoystickLeft_Yaxis,JoystickRight_Xaxis, JoystickRight_Yaxis.

But in the below Pin Change interrupt code, it just calls the pins “latest_interrupted_pin”

Also, if my calculations are correct, using delay(20); will mean i’m reading the 4 RC signals at 50Hz. I’m just choosing that Hz because it seems like a reasonable number that the Uno can handle, from reading online.

Thanks in advance for any insights on how to modify the below code to read and serially print 4 rc signals

#include <PinChangeInt.h>
 
#define MY_PIN1 5 // we could choose any pin
#define MY_PIN2 6 // we could choose any pin
#define MY_PIN3 7 // we could choose any pin
#define MY_PIN4 8 // we could choose any pin
 
volatile int pwm_value = 0;
volatile int prev_time = 0;
uint8_t latest_interrupted_pin;
 
void rising()
{
  latest_interrupted_pin=PCintPort::arduinoPin;
  PCintPort::attachInterrupt(latest_interrupted_pin, &falling, FALLING);
  prev_time = micros();
}
 
void falling() {
  latest_interrupted_pin=PCintPort::arduinoPin;
  PCintPort::attachInterrupt(latest_interrupted_pin, &rising, RISING);
  pwm_value = micros()-prev_time;
  Serial.println(pwm_value);
}
 
void setup() {
  pinMode(MY_PIN1, INPUT); digitalWrite(MY_PIN, HIGH);
  pinMode(MY_PIN2, INPUT); digitalWrite(MY_PIN, HIGH);
  pinMode(MY_PIN3, INPUT); digitalWrite(MY_PIN, HIGH);
  pinMode(MY_PIN4, INPUT); digitalWrite(MY_PIN, HIGH);
  Serial.begin(115200);
  PCintPort::attachInterrupt(MY_PIN1, &rising, RISING);
  PCintPort::attachInterrupt(MY_PIN2, &rising, RISING);
  PCintPort::attachInterrupt(MY_PIN3, &rising, RISING);
  PCintPort::attachInterrupt(MY_PIN4, &rising, RISING);
}
 
void loop() { 
delay(20);
}

exwhyzed: void loop() { delay(20); }

I'm afraid this is beyond me. The way things are here, you do a bit of setting up and then spend the rest of your life pointlessly whizzing around a loop fifty times a second, doing nothing.

exwhyzed: Also, if my calculations are correct, using delay(20); will mean i'm reading the 4 RC signals at 50Hz.

The absolutely very last thing you need in your program is the delay() function. The Arduino can do nothing during a delay(). You should be arranging for your program to do stuff as quickly as possible.

Apart from that your concept of collecting data by doing something 50 times per second is fundamentally flawed because you have no means to synchronize your 50Hz with the incoming signal.

Pin change interrupts are a little more complex because your code has to figure out which interrupt was triggered.

Write some code (as short as possible) that detects one pin change interrupt and then gradually extend it to more pins. You don't learn to swim in the middle of the Pacific Ocean.

...R

Ok I’m nearing the finish line on this one…I hope! Since creating this thread, I’ve come across numerous examples of people reading RC receiver signals with an arduino, with full documentation and code. Why didn’t I see these before?

Anyhow, keeping my wiring the same, I can use the below code for reading a single pin, using PinChangeInt way, but when I try any of the multi-pin PinChangeInt examples, nothing but some initial text to describe what is going on prints on the serial monitor. When i re-upload the single pin PinChangeInt code, the numbers start showing up on the serial monitor again. I’m not sure why my UNO agrees with the single pin code, but disagrees with the multi-pin code.

Single pin reading code that works

#include <PinChangeInt.h>
 
#define MY_PIN 10 // we could choose any pin
 
volatile int pwm_value = 0;
volatile int prev_time = 0;
uint8_t latest_interrupted_pin;
 
void rising()
{
  latest_interrupted_pin=PCintPort::arduinoPin;
  PCintPort::attachInterrupt(latest_interrupted_pin, &falling, FALLING);
  prev_time = micros();
}
 
void falling() {
  latest_interrupted_pin=PCintPort::arduinoPin;
  PCintPort::attachInterrupt(latest_interrupted_pin, &rising, RISING);
  pwm_value = micros()-prev_time;
  Serial.println(pwm_value);
}
 
void setup() {
  pinMode(MY_PIN, INPUT); digitalWrite(MY_PIN, HIGH);
  Serial.begin(115200);
  pinMode(9, OUTPUT);  // this pin will pull the XM-15B CE pin HIGH to enable the module
  digitalWrite(9, HIGH);
  PCintPort::attachInterrupt(MY_PIN, &rising, RISING);
}
 
void loop() { }

And here’s the 3 pin code that doesn’t work.

I get no compiling errors from the IDE on the non-working code, its just that no PWM numbers are showing up in the serial monitor as they should be. Is it some rule-of-thumb that i’ve missed somewhere?

/*
Copyright 2011 Lex Talionis (Lex.V.Talionis at gmail)
This program is free software: you can redistribute it 
and/or modify it under the terms of the GNU General Public 
License as published by the Free Software Foundation, 
either version 3 of the License, or (at your option) any 
later version.

This code uses pin change interrupts and timer 1 to mesure the 
time between the rise and fall of 3 channels of PPM 
(Though often called PWM, see http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1253149521/all)
on a typical RC car reciver.  It could be extended to as
many channels as you like.  It uses the PinChangeInt library
to notice when the signal pin goes high and low, and the 
Timer1 library to record the time between.


*/
#include <PinChangeInt.h>    // http://playground.arduino.cc/Main/PinChangeInt
#include <PinChangeIntConfig.h>
#include <TimerOne.h>        // http://playground.arduino.cc/Code/Timer1

#define NO_PORTB_PINCHANGES //PinChangeInt setup
#define NO_PORTC_PINCHANGES    //only port D pinchanges (see: http://playground.arduino.cc/Learning/Pins)
#define PIN_COUNT 3    //number of channels attached to the reciver
#define MAX_PIN_CHANGE_PINS PIN_COUNT

#define RC_CH1 10    //arduino pins attached to the reciver
#define RC_CH2 11
#define RC_CH3 12
byte pin[] = {RC_CH1, RC_CH2, RC_CH3};    //for maximum efficency thise pins should be attached
unsigned int time[] = {0,0,0};                // to the reciver's channels in the order listed here

byte state=0;
byte burp=0;    // a counter to see how many times the int has executed
byte cmd=0;     // a place to put our serial data
byte i=0;       // global counter for tracking what pin we are on

void setup() {
    Serial.begin(115200);
    pinMode(9, OUTPUT);  // this pin will pull the XM-15B CE pin HIGH to enable the module
    digitalWrite(9, HIGH);
    Serial.print("PinChangeInt ReciverReading test");
    Serial.println();            //warm up the serial port

    Timer1.initialize(2200);    //longest pulse in PPM is usally 2.1 milliseconds,
                                //pick a period that gives you a little headroom.
    Timer1.stop();                //stop the counter
    Timer1.restart();            //set the clock to zero

    for (byte i=0; i<3; i++)
    {
        pinMode(pin[i], INPUT);     //set the pin to input
        digitalWrite(pin[i], HIGH); //use the internal pullup resistor
    }
    PCintPort::attachInterrupt(pin[i], rise,RISING); // attach a PinChange Interrupt to our first pin
}

void loop() {
    cmd=Serial.read();        //while you got some time gimme a systems report
    if (cmd=='p')
    {
        Serial.print("time:\t");
        for (byte i=0; i<PIN_COUNT;i++)
        {
            Serial.print(i,DEC);
            Serial.print(":");
            Serial.print(time[i],DEC);
            Serial.print("\t");
        }
        Serial.print(burp, DEC);
        Serial.println();
/*      Serial.print("\t");
        Serial.print(clockCyclesToMicroseconds(Timer1.pwmPeriod), DEC);
        Serial.print("\t");
        Serial.print(Timer1.clockSelectBits, BIN);
        Serial.print("\t");
        Serial.println(ICR1, DEC);*/
    }
    cmd=0;
    switch (state)
    {
        case RISING: //we have just seen a rising edge
            PCintPort::detachInterrupt(pin[i]);
            PCintPort::attachInterrupt(pin[i], fall, FALLING); //attach the falling end
            state=255;
            break;
        case FALLING: //we just saw a falling edge
            PCintPort::detachInterrupt(pin[i]);
            i++;                //move to the next pin
            i = i % PIN_COUNT;  //i ranges from 0 to PIN_COUNT
            PCintPort::attachInterrupt(pin[i], rise,RISING);
            state=255;
            break;
        /*default:
            //do nothing
            break;*/
    }
}

void rise()        //on the rising edge of the currently intresting pin
{
    Timer1.restart();        //set our stopwatch to 0
    Timer1.start();            //and start it up
    state=RISING;
//  Serial.print('r');
    burp++;
}

void fall()        //on the falling edge of the signal
{
    state=FALLING;
    time[i]=readTimer1();    // read the time since timer1 was restarted
//  time[i]=Timer1.read();    // The function below has been ported into the
                            // the latest TimerOne class, if you have the
                            // new Timer1 lib you can use this line instead
    Timer1.stop();
//  Serial.print('f');
}

unsigned long readTimer1()        //returns the value of the timer in microseconds
{                                    //rember! phase and freq correct mode counts 
                                    //up to ICR1 then down again
    unsigned int tmp=TCNT1;
    char scale=0;
    switch (Timer1.clockSelectBits)
    {
    case 1:// no prescalse
        scale=0;
        break;
    case 2:// x8 prescale
        scale=3;
        break;
    case 3:// x64
        scale=6;
        break;
    case 4:// x256
        scale=8;
        break;
    case 5:// x1024
        scale=10;
        break;
    }
    while (TCNT1==tmp) //if the timer has not ticked yet
    {
        //do nothing -- max delay here is ~1023 cycles
    }
    tmp = (  (TCNT1>tmp) ? (tmp) : (ICR1-TCNT1)+ICR1  );//if we are counting down add the top value
                                                        //to how far we have counted down
    return ((tmp*1000L)/(F_CPU /1000L))<<scale;
}

Here’s another example of multi-pin code that also doesn’t work

// MultiChannels
//
// rcarduino.blogspot.com
//
// A simple approach for reading three RC Channels using pin change interrupts
//
// See related posts - 
// http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
// http://rcarduino.blogspot.co.uk/2012/03/need-more-interrupts-to-read-more.html
// http://rcarduino.blogspot.co.uk/2012/01/can-i-control-more-than-x-servos-with.html
//
// rcarduino.blogspot.com
//

// include the pinchangeint library - see the links in the related topics section above for details
#include <PinChangeInt.h>

#include <Servo.h>

// Assign your channel in pins
#define THROTTLE_IN_PIN 10
#define STEERING_IN_PIN 11
#define AUX_IN_PIN 12

// Assign your channel out pins
#define THROTTLE_OUT_PIN 5
#define STEERING_OUT_PIN 6
#define AUX_OUT_PIN 7

// Servo objects generate the signals expected by Electronic Speed Controllers and Servos
// We will use the objects to output the signals we read in
// this example code provides a straight pass through of the signal with no custom processing
Servo servoThrottle;
Servo servoSteering;
Servo servoAux;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2
#define AUX_FLAG 4

// holds the update flags defined above
volatile uint8_t bUpdateFlagsShared;

// shared variables are updated by the ISR and read by loop.
// In loop we immediatley take local copies so that the ISR can keep ownership of the 
// shared ones. To access these in loop
// we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on
// as quickly as possible, this ensures that we are always able to receive new signals
volatile uint16_t unThrottleInShared;
volatile uint16_t unSteeringInShared;
volatile uint16_t unAuxInShared;

// These are used to record the rising edge of a pulse in the calcInput functions
// They do not need to be volatile as they are only used in the ISR. If we wanted
// to refer to these in loop and the ISR then they would need to be declared volatile
uint32_t ulThrottleStart;
uint32_t ulSteeringStart;
uint32_t ulAuxStart;

void setup()
{
  Serial.begin(9600);
  
  Serial.println("multiChannels");

  // attach servo objects, these will generate the correct 
  // pulses for driving Electronic speed controllers, servos or other devices
  // designed to interface directly with RC Receivers  
  servoThrottle.attach(THROTTLE_OUT_PIN);
  servoSteering.attach(STEERING_OUT_PIN);
  servoAux.attach(AUX_OUT_PIN);

  // using the PinChangeInt library, attach the interrupts
  // used to read the channels
  PCintPort::attachInterrupt(THROTTLE_IN_PIN, calcThrottle,CHANGE); 
  PCintPort::attachInterrupt(STEERING_IN_PIN, calcSteering,CHANGE); 
  PCintPort::attachInterrupt(AUX_IN_PIN, calcAux,CHANGE); 
}

void loop()
{
  // create local variables to hold a local copies of the channel inputs
  // these are declared static so that thier values will be retained 
  // between calls to loop.
  static uint16_t unThrottleIn;
  static uint16_t unSteeringIn;
  static uint16_t unAuxIn;
  // local copy of update flags
  static uint8_t bUpdateFlags;

  // check shared update flags to see if any channels have a new signal
  if(bUpdateFlagsShared)
  {
    noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables

    // take a local copy of which channels were updated in case we need to use this in the rest of loop
    bUpdateFlags = bUpdateFlagsShared;
    
    // in the current code, the shared values are always populated
    // so we could copy them without testing the flags
    // however in the future this could change, so lets
    // only copy when the flags tell us we can.
    
    if(bUpdateFlags & THROTTLE_FLAG)
    {
      unThrottleIn = unThrottleInShared;
    }
    
    if(bUpdateFlags & STEERING_FLAG)
    {
      unSteeringIn = unSteeringInShared;
    }
    
    if(bUpdateFlags & AUX_FLAG)
    {
      unAuxIn = unAuxInShared;
    }
     
    // clear shared copy of updated flags as we have already taken the updates
    // we still have a local copy if we need to use it in bUpdateFlags
    bUpdateFlagsShared = 0;
    
    interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
    // as soon as interrupts are back on, we can no longer use the shared copies, the interrupt
    // service routines own these and could update them at any time. During the update, the 
    // shared copies may contain junk. Luckily we have our local copies to work with :-)
  }
  
  // do any processing from here onwards
  // only use the local values unAuxIn, unThrottleIn and unSteeringIn, the shared
  // variables unAuxInShared, unThrottleInShared, unSteeringInShared are always owned by 
  // the interrupt routines and should not be used in loop
  
  // the following code provides simple pass through 
  // this is a good initial test, the Arduino will pass through
  // receiver input as if the Arduino is not there.
  // This should be used to confirm the circuit and power
  // before attempting any custom processing in a project.
  
  // we are checking to see if the channel value has changed, this is indicated  
  // by the flags. For the simple pass through we don't really need this check,
  // but for a more complex project where a new signal requires significant processing
  // this allows us to only calculate new values when we have new inputs, rather than
  // on every cycle.
  if(bUpdateFlags & THROTTLE_FLAG)
  {
    if(servoThrottle.readMicroseconds() != unThrottleIn)
    {
      servoThrottle.writeMicroseconds(unThrottleIn);
    }
  }
  
  if(bUpdateFlags & STEERING_FLAG)
  {
    if(servoSteering.readMicroseconds() != unSteeringIn)
    {
      servoSteering.writeMicroseconds(unSteeringIn);
    }
  }
  
  if(bUpdateFlags & AUX_FLAG)
  {
    if(servoAux.readMicroseconds() != unAuxIn)
    {
      servoAux.writeMicroseconds(unAuxIn);
    }
  }
  
  bUpdateFlags = 0;
}


// simple interrupt service routine
void calcThrottle()
{
  // if the pin is high, its a rising edge of the signal pulse, so lets record its value
  if(digitalRead(THROTTLE_IN_PIN) == HIGH)
  { 
    ulThrottleStart = micros();
  }
  else
  {
    // else it must be a falling edge, so lets get the time and subtract the time of the rising edge
    // this gives use the time between the rising and falling edges i.e. the pulse duration.
    unThrottleInShared = (uint16_t)(micros() - ulThrottleStart);
    // use set the throttle flag to indicate that a new throttle signal has been received
    bUpdateFlagsShared |= THROTTLE_FLAG;
  }
}

void calcSteering()
{
  if(digitalRead(STEERING_IN_PIN) == HIGH)
  { 
    ulSteeringStart = micros();
  }
  else
  {
    unSteeringInShared = (uint16_t)(micros() - ulSteeringStart);
    bUpdateFlagsShared |= STEERING_FLAG;
  }
}

void calcAux()
{
  if(digitalRead(AUX_IN_PIN) == HIGH)
  { 
    ulAuxStart = micros();
  }
  else
  {
    unAuxInShared = (uint16_t)(micros() - ulAuxStart);
    bUpdateFlagsShared |= AUX_FLAG;
  }
}

I would just write some simple code without any library - because I don't know how the library works.

If you read the Atmel datasheet you can see the registers that are used within the MCU to record stuff about pin change interrupts.

The ISR is defined as (from Nick Gammon's tutorial)

ISR (PCINT0_vect) 
 {
  // one of pins D8 to D13 has changed
 }

Then just add some code that identifies which pin has caused the interrupt.

...R