Controlling a digital potentiometer with an Android device over BLE using Arduino.

I have been wanting to write a generic article/tutorial that I could use as a reference for future projects since a while ago. By reading the title, the reader might think it is very specific in terms of the the technology involved, but if explained properly, adapting the procedures described here to a project that uses equivalent technology shouldn’t be too difficult.

Let’s start describing a bit more in depth the parties involved:

MCU

For the MCU I have used an Arduino Nano for controlling the digital potentiometer over SPI and communicating to the BLE module over serial communication. Any Arduino board can be used here, just keeping in mind the little differences.

BLE module

I’ve used HM-10 Bluetooth Low Energy module based on Texas instrument CC2540/CC2541. It is an arduino-friendly module, easy to configure using AT command via serial port. It comes with a built in characteristic that can be used as an I/O port, that takes any value written onto it and forwards it to the serial port and vice versa. In this simple example, we use that characteristic to represent the potentiometer. In a more complicated project with several actuators, you could still use a single I/O port module but you would have to design a command based protocol to access the other actuators. My personal preference is having one characteristic for every single thing you want to control in a system, so the value of each characteristic represents the state of the thing you control. That would allow you to subscribe to notifications produced every time the value of a characteristic changes.

Digital potentiometer

For this I’m using a MCP41100 100k digital pot with SPI interface from Microchip. Again, you could use any chip with SPI interface, but just keeping mind that the manufacturer could use a different command for setting the value of the potentiometer and of course the pinout may vary with different manufacturers. You may prefer to use I2C components that also work very well with arduino, but in this simple example I’ve used an SPI interfaced component.

Android device and app

Bluetooth Low Energy is just supported by android devices with OS version 4.3 or higher. I didn’t want to make a complicated application with tons of dependencies, tests or fancy UI/UX. After all, this is just a proof o concept, and anything extra added could get in the way of the understanding exercise. Having said that, during the last year I’ve been coding with the functional reactive programming mindset, so because of that and for convenience I’ve used a BLE third party library built using that architecture. But this article is not about software architecture nor best practices. I take that small concession as well as the use of lambda expressions that make the code much tidier, but I understand it could be not as easy to read as normal notation.

All the code for both arduino sketches and android app, can be found on this github project.

Let’s get this party started, shall we?

BLE module and MCU talking

The very first thing we have to do here is to connect the two modules so they can have a proper conversation. For doing that, we have to use some of the arduiono’s digital I/Os and set them as a serial port as well as VCC and ground. In this case I’ve used D2 and D3. In most of the examples I’ve seen online, they use terminals D0 and D1 that are the ones used by default for serial communication. For me, at least for this example, it makes more sense to have the serial port that we control, in were we can type default values if we want from the serial monitor, then process that information and forward it where we want. Our pinout connection from arduino to ble module would be then:

5V — VCC
GND — GND
D2(RX) — TXD
D3(TX) - RXD
(arduino left, hm-10 right)
HM-10 to Arduino Nano connection

Once the wiring is done, we write a simple sketch that relays all the info from one serial port to the other.

#include <SoftwareSerial.h>
SoftwareSerial bleSerial(2, 3); // RX, TX
void setup() {
//initialize serial port for logs
Serial.begin(9600);
while (!Serial) {
}
  bleSerial.begin(9600);
}
void loop() {
  if (bleSerial.available()) {
Serial.write(bleSerial.read());
}

if (Serial.available()) {
bleSerial.write(Serial.read());
}
}

After we load this simple sketch. We can open the serial monitor and type on it. That will send the data directly to the BLE module and modify the value of our I/O characteristic. It will also accept the AT commands we mentioned earlier, so at this point we could just give our module a friendly name by just typing AT+NAME followed by a friendly name like BleDigipot or any other name that you fancy.

If you have access to BLE software tools like LightBlue for OS X, you can check that the things you type on the serial port, other than AT commands, change the value of the I/O characteristic. I forgot to mention that the I/O characteristic has a 16bit generic UUID with value FFE1.

Adding the potentiometer

We are using a chip with SPI interface which requires the use of a clock signal (SCK), chip selection (CS) and signal input (SI), as well as the usual Vcc and gnd. It might seem that SPI takes a lot of I/O ports from your microcontroller, but the nice thing here is that adding more digipots will require just one extra I/O port for CS so the other ports would be reused. Following the ports commonly used for SPI in Arduino, this would stay like this:

Complete circuit

The remaining terminals of the digital potentiometer are the terminals of the potentiometer itself. Later we could connect terminals 5 and 6, or 6 and 7 to a digital multimeter in order to measure the impedance.

Now using the first sketch we wrote as a base, we just have to add the communication with the SPI interface and program the reaction to the serial data that comes from the BLE port. BLE communication is normally limited to 20bytes per message, and our digital pot can only take 256 possible values, so we will only need first byte of that message (2⁸=256). For the sake of doing things simple, we are gonna read byte by byte and transfer each received one to the potentiometer, so we are gonna have to be careful when sending the information, otherwise we will be changing the value of the potentiometer as many times as the length in bytes of the message.

// include the SPI library:
#include <SPI.h>
#include <SoftwareSerial.h>
SoftwareSerial bleSerial(2, 3); // RX, TX
// set pot select pin
const int potCS = 10;
const int potWriteCmd = B00010011;
void setup() {
// set output pins:
pinMode(potCS, OUTPUT);
  // initialize SPI:
SPI.begin();
  //initialize serial port for logs
Serial.begin(9600);
while (!Serial) {
}
  bleSerial.begin(9600);
}
void loop() {
if (bleSerial.available()) {
int val = bleSerial.read();
Serial.write("value = ");
Serial.print(val,DEC);
Serial.write("\r\n");
       digitalPotWrite(val);
}

if (Serial.available()) {
bleSerial.write(Serial.read());
}
}
void digitalPotWrite(int value) {
// put the CS pin to low in order to select the chip:
digitalWrite(potCS, LOW);
    SPI.transfer(potWriteCmd);
SPI.transfer(value);
    // put the CS pin to high for transferring the data
digitalWrite(potCS, HIGH);
}

We start by including the SPI library and defining the constant for the CS pin in the arduino that in our case is number 10. We define another constant for the command that let us set the value on the digital pot, we found this by reading the datasheet of the chip. In the setup function, we set the pin 10 as output and initialize SPI. In the loop function, as I said before we’ve changed it to send each received byte to the potentiometer, and for doing that we’ve wrote a simple function that takes a value and sends it. It first set the select pin to low, transfer the command, then the data and after this it sets the select pin up again to make the changes effective.

Of course, at this point you want to check wether or not things work. You can now measure the value of the digital pot and change the value with any software tool like LightBlue, but you can also download an app such as BLE Scanner and do the same. The UUIDs for the service and encapsulated characteristic are 16bit generic Bluetooth UUID. When writing the app, we will have to specify the rest of the address to complete a 128 bit UUID. They are FFE0 for the service and FFE1 for the characteristic.

If we write a hex value and it gets reflected in the multimeter following the formula (value x 100)/256, we can feel satisfied. Don’t forget the conversion from decimal to hexadecimal.

Android joins the party

Now we need a friendly interface to control our cool BLE potentiometer, so why not inviting our old friend Android to help us with that and why not … bring more drinks. Jokes aside, we just need an application with two screens: one for presenting the devices so we chose the one we are going to connect and another screen for connecting and controlling the impedance of our wireless potentiometer. As mentioned before, all the code including the app can be found in this github project. For the bluetooth communication, we use the library mentioned at the beginning of the article.

We have to add the permissions to our manifest to start with.

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

We obviously need the Bluetooth permission, but also need the any location permission so we can scan ble devices. The bluetooth admin permission is just for bonding the device, so we can subscribe to notifications.

After that, we create an instance of the ble client and we share across the app as a singleton. This can be achieved by using dependency injection library like Dagger, but in this case I just used the application class.

For the first screen, we create an activity containing a simple ListView that will be populated using an ArrayAdapter. A simple ItemClickListener will monitor the user input when selecting a device. We make use of the lifecycle of the activity for starting, and stoping the device scan. Reactive extension make this process very easy and tidy, the activity subscribes to a stream of search results on its starting method and unsubscribe to this stream on the finishing method. It doesn’t matter much if we use either onStart/onStop or onResume/onPause.

@Override
protected void onResume() {
super.onResume();
bleDiscoverySubscription = subscribeToBLEDiscovery();

}


@Override
protected void onPause() {
if (bleDiscoverySubscription != null)
bleDiscoverySubscription.unsubscribe();
super.onPause();
}

private Subscription subscribeToBLEDiscovery() {
return rxBleClient.scanBleDevices(UUID.fromString(UUID_SERVICE))
.timeout(4, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.map(RxBleScanResult::getBleDevice)
.map(RxBleDevice::getBluetoothDevice)
.subscribe(bleDevice ->
((ArrayAdapter) listView.getAdapter()).add(bleDevice)
);
}

The scan method returns an observable object that is a stream of scanning results that can be transformed by mappings and other operations. By subscribing to this observable object, we define how we react every time an object of the type we are expecting, is emitted. The alternative would have been implementing a ScanResult listener and triggering the ble device scan. Anyway, the reaction in our case is filling our list with devices. Although we are just working with one ble device, we want the user to be able to choose the device.

Simple activity for selecting the device

When clicked on the device row, the activity bundles the selected device and launches the second activity passing this information. If we want to subscribe to notifications, we also have to bond the device with our phone.

@Override
public void onItemClick(AdapterView<?> adapter,View v,int i,long l){
BluetoothDevice bleDevice = (BluetoothDevice) adapter
.getItemAtPosition(i);

if (bleDevice.getBondState() != BluetoothDevice.BOND_BONDED)
bleDevice.createBond();

Intent intent = new Intent(this, MainActivity.class);
intent.putExtra(BK_DEVICE, bleDevice);
startActivity(intent);
finish();
}

On the main activity we again make use of the lifecycle for subscribing and unsubscribing to events, but in this case we are going to establish a connection and also subscribe to the connection state.

@Override   
protected void onStart() {
super.onStart();
bleSubscription.add(establishConnection());
bleSubscription.add(subscribeToConnectionState());
}
@Override
protected void onStop() {
bleSubscription.clear();
super.onStop();
}
private Subscription establishConnection() {
return rxBleClient.getBleDevice(device.getAddress())
.establishConnection(this, false)
.share()
.subscribe(connection -> this.connection = connection,
throwable -> Log.w(TAG, throwable.toString()));
}
private Subscription subscribeToConnectionState() {
return rxBleClient.getBleDevice(device.getAddress())
.observeConnectionStateChanges()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(connectionState -> {
if (connectionState.equals(CONNECTED))
showToast("Connected");
else if (connectionState.equals(DISCONNECTED)){
showToast("Disconnected");
Intent intent = new Intent(this
,SelectServiceActivity.class);
startActivity(intent);
finish();
}
},
throwable -> Log.w(TAG, throwable.toString()));
}

With the first subscription, we are just retaining the emitted connection in a field, so we can communicate afterwards. With the second subscription, we react by showing a message with the connection event, then closing the activity if disconnected. Once, the connection has been established, we can read, write or subscribe to notifications on a specific characteristic. It might seem strange that the connection comes from an observable as well, but the process of establishing a connection is an asynchronous one that can be modeled by the reactive pattern.

For the UI we have a simple SeekBar with at TextView to represent the percentage of the potentiometer.

Main activity

We set a listener on the SeekBar, so when the user changes the value of the SeekBar, it writes onto our I/O characteristic.

@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean fromUser) {
value.setText(i + "%");

if (fromUser && connected) {
byte[] data = new byte[]{percentageToByte(i)};
UUID uuid = UUID.fromString(UUID_CHARACTERISTIC);
Subscription writeSubs = connection
.writeCharacteristic(uuid,data)
.subscribe();
bleSubscription.add(writeSubs);
}
}

In this case, we don’t react to the result of the operation, but the subscription is necessary in order to execute the call.

We are missing a cool thing here, update the UI when the characteristic value changes. For doing that, we need to subscribe to the notifications. We can achieve this by slightly modifying the connection subscription.

private Subscription establishConnAndSubscribeToNotifications() {
Observable<RxBleConnection> bleObservable = rxBleClient
.getBleDevice(device.getAddress())
.establishConnection(this, false)
.share();

final UUID uuid = UUID.fromString(UUID_CHARACTERISTIC);

Subscription subscription = bleObservable
.doOnNext(connection -> this.connection = connection)
.flatMap(connection ->
Observable.merge(connection.readCharacteristic(uuid), connection.setupNotification(uuid).flatMap(notificationObservable -> notificationObservable)))
.filter(Utils::notNull)
.filter(bytes -> bytes.length > 0)
.map(bytes -> bytes[0])
.map(Utils::byteToPercentage)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(progress -> pot.setProgress(progress),
throwable -> Log.w(TAG, throwable));
return subscription;

}

In this case we use the flatmap operator that converts a event into another observable. We are combining also two observables: one that read the current value of the characteristic and another that subscribes to its notifications. Note that the last thing is not possible if the device is not bonded. At a first glance, it seems a bit complicated: transforming and combining streams, but ble in itself requires a set of asynchronous operations in which some of them depend on previous ones, i.e. characteristic writing and service discovery. To me, this seems like a very neat way of handling ble communication.

We can now run our app, connect to the potentiometer and measure with the multimeter.

I hope that you found this article interesting enough. Don’t hesitate in commenting if you have any question.