IOS App for Adafruit Feather NRF52832

by akiofujita in Circuits > Arduino

7685 Views, 18 Favorites, 0 Comments

IOS App for Adafruit Feather NRF52832

iOS-BLE-Pic.PNG

The Adafruit Feather nRF52832 has a BLE (Bluetooth Low Energy) functionality that has a lot of potential to be used in many different projects. In this Instructable, I will be stepping through a project where I created an iOS application that would receive data sent from the Feather and graph it.

Just a few things to note before we begin.

This app was made with the following versions:

  • macOS Big Sur (Version 11.1)
  • Xcode (Version 12.4)
  • iOS (Version 14.4)

For different software versions, especially Xcode, some of my instructions may not apply exactly. For the most part, slight version differences should be okay, but if you encounter issues, Google is your best friend.

Here is a link to my code (both Arduino and iOS) for your reference: https://github.com/shaqattack13/iOS-BLE

Alright, let's get into it!

Supplies

  • Adafruit Feather nRF52832
  • Arduino Application
  • Xcode Application (only available on Macs)
  • Apple device (iPhone / iPad / iPod)

Setting Up the Arduino Code

nrf52832.jpg
ios-ble-pic1.JPG
bleuart-pic1.JPG
bleuart-pic2.JPG
bleuart-pic3.JPG
bleuart-pic4.JPG
bleuart-pic5.JPG

Before getting started on the iOS app development, I had to set up the Adafruit Feather nRF52832 first. I did this by uploading code to it via the Arduino IDE because the Feather is compatible with Arduino. In order to do this, I needed to download the board definition for the nRF52832.

Downloading the nRF52 Board Definition

Navigate to the Arduino Preferences menu.

On Macs: Arduino -> Preferences -> Additional Boards Manager URLs (located at bottom of the window)

On Windows: File -> Preferences -> Additional Boards Manager URLs (located at bottom of the window)

Copy and paste the following links into “Additional Boards Manager URLs.” Make sure each link is in its own separate line.

Now, open the Arduino IDE and go to Tools -> Board -> Boards Manager. Search for "Adafruit nRF52". When found, install the board definition (shown in the image). This step is necessary if you want to upload code to the Feather.

Update the nRF52832 Bootloader

Bootloader Tutorial

Note: Your bootloader version will be different than what’s in the picture. Don’t worry about that.

Code Changes to bleuart.ino

The nRF52832 board definition includes a nice example code that I slightly altered to make the Feather collect data from one of its pins and send it to the iOS app. In order to find this code, make sure the Adafruit Feather nRF52832 board is selected:

Tools -> Board -> Adafruit nRF52 Boards -> Adafruit Feather nRF52832

Then, you will be able to find the example code here:

File -> Examples -> Adafruit Bluefruit nRF52 Libraries -> Peripheral -> bleuart

I made a few changes to this file, which I've described below (describing code changes in words is difficult, so I have attached screenshots as a guide).

The first change is at the "void loop()" function (near line 103). Replace "delay(20); ... bleuart.write( buf, count );" lines (near lines 109-113) with this code:

// Delay to wait for enough input, since we have a limited transmission buffer
delay(500);
char buf[64];

// Read value from pin A0 of the nRF52832
int num = analogRead(A0);

// Convert integer value of "num" to String, since ble only works with characters, not integers
String value = String(num);

// Convert String into an array of characters that will be stored in "buf"
value.toCharArray(buf, 64);

// Send the buf (character array) via BLE
bleuart.write( buf, strlen(buf) );

This will essentially read the value at the analog pin A0, convert the value into text form, and send the text to the app.

Another change we should make is the while loop condition: while (Serial.available()). Currently, the while-loop will run only when something is typed and entered into the Serial monitor. However, it would be better to automate the process of reading the data from the Feather and sending it to the app. So, I created a variable that would keep track of whether or not the Feather and app are connected. Here is what I did:

line 23 (under BLEBas blebas;) -> add this line:

bool BLEisConnected = false;

line 107 -> Change while loop condition from while (Serial.available()) to

while (BLEisConnected)

line 136 (in void connect_callback() function) -> add this line:

BLEisConnected = true;

line 152 (in void disconnect_callback() function) -> add this line:

BLEisConnected = false;

Upload this code to the Feather.

Now that we have the Feather set up, we will dive into the iOS part of this project.

Open New IOS Project

step1-1.png
Screen Shot 2021-06-27 at 6.29.36 PM.png

Open the Xcode Application and create a new project. Our app will just be a generic app on the iPhone, which runs on the iOS operating system. In the project template display, choose the iOS platform, select App, and click Next.

The following screen shows various basic settings that must be completed to create the project. Pick any name for the Product Name.

Select a development team if you have one. If you don't have one, sign up here: (https://developer.apple.com/programs/). This will be needed if you want to run the app on your iPhone. The Bluetooth functionality will not work on the simulator so running the app on the iPhone/iPad is highly recommended.

For the Organization Identifier, enter something in the form of "com.". I used my username here. This Organization Identifier, in combination with the Product Name, will be used to create a Bundle Identifier, which is a unique identifier specific to the app. This Bundle Identifier is needed if you submit your app to the App Store.

The other settings should remain as default; refer to the screenshot to ensure that all settings match. The Interface we're using is Storyboard. This will be the best option for beginners. There is only one Life Cycle option: UIKit App Delegate. This is essentially a system that manages the various screens associated with the app. Finally, we will be using the Swift language which is easier to read and learn than Objective-C. Do not select "Use Core Data" and do not "Include Tests". Once all settings are confirmed, click Next.

Save your project at any location you'd like. Click Create when you've found the chosen folder.

Configure Necessary Settings

step2-1.png
step2-2.png

Set up Info.plist

Before getting started on the actual project, there are some settings that need to be configured first. At this point, you should see a bigger settings page. If not, click on the blue icon with your project name in the top left. Click the Info tab. Next, if you hover over any of the Keys, you'll see a + sign appear. Once you click on it, search for the Privacy - Bluetooth Always Usage Description key. Repeat the same process for the Privacy - Peripheral Usage Description key. The reason for adding these two keys is to allow the app to notify the user that the Bluetooth service needs to be turned on in order to proceed with the app as well as connect to peripherals.


Install CocoaPods

In order to import frameworks (or library/module), CocoaPods must be installed first. Open the terminal and "cd" into the project's directory. If you run the "ls" command, you should see the .xcodeproj file for your app. Then, check if you have an Intel or an M1 Mac. Click the Apple logo in the top left corner of your screen, then select “About this Mac.” You should see “Intel” or “Apple M1” somewhere in the dialog box.

Intel Macs

Tutorial Source

Type the following command into the terminal and click enter.

sudo gem install cocoapods

M1 Macs

Tutorial Source

Type the following commands into Terminal and hit enter. These are 3 different commands, so enter each command separately.

sudo arch -x86_64 gem install ffi 
sudo gem install cocoapods
arch -x86_64 pod init


Installing Charts Module

For the next phase, we will be importing a framework, which is basically Swift's equivalent of a library or module. This framework, Charts, can be used to make all sorts of graphs, but we will be using only the Line Chart feature. In order to import this, you first need to open the terminal and "cd" into the project's directory. If you run the "ls" command, you should see the .xcodeproj file.

Here are the commands necessary for the following steps for your convenience:

Intel Macs

pod init
open Podfile
pod install


M1 Macs

arch -x86_64 pod init
open Podfile
arch -x86_64 pod install


Follow the short clip below to help you install the module if you need it (Watch from 2:15 to 4:20).


Once the Charts module is installed, you're ready to begin the programming part!

Design the User Interface With Storyboard

step3-1.png
step3-2.png
Storyboard Objects For Instructables

With the general settings configured, we will now enter the Xcode environment and start creating the app.

In the File Explorer (left side), select the Main.storyboard file. A blank iPhone screen should appear. We will be adding a few basic controls to this screen. I will be referring to this screen as the Storyboard.

At the bottom, you should see the words iPhone 11 with an iPhone icon to the left of it. Click on that. In this section, you will be able to choose the device that you'd like to design in. You can also change the orientation, which we'll come back to later. Because I own an iPhone 8 Plus, I will be selecting that device; however, yours may be different. Because Apple has made many different devices of various sizes, you will be able to make slightly different designs for different iPhones/iPads as well as for different orientations.

Next, click the + sign at the top right. Find the Navigation Bar object. Click and drag that object towards the top of the Storyboard. Double-click on the word Title. You should be able to change it to whatever you want. I named mine iOS-BLE. In a similar manner, you should add an arrow.clockwise (refreshBtn), 2 labels (showGraphLbl and connectStatusLbl), and a switch (showGraphSw). The process is shown in the video. It is important that you press enter after typing to confirm what you typed; otherwise, it won't get saved. You can see my mistake with the refreshBtn in the video, where the name didn't get saved. We will be utilizing all of these objects in our app.

Now that we've got the necessary objects on the Storyboard, we will need to specify where exactly these objects will be located on the screen.

Navigation Bar Constraints

Navigation Bar Constraints
Screen Shot 2021-04-18 at 7.30.12 PM.png

Next, let's add constraints for each object. The attached video should be sufficient to guide you through the constraints for the Navigation Bar. Note that you must uncheck the Constrain to margins box before making constraints. Otherwise, the constraints will be measured relative to the margins of the screen instead of nearby objects. We do not want that for our purposes.

In addition, you should check the constraints that you've made by clicking on the Constraints drop-down in the middle left portion of the screen (shown in the picture).

Show Graph Label Constraints

showGraphLabel Constraints

The attached video should be sufficient to guide you through the showGraphLbl constraints. Note that you MUST uncheck the Constrain to margins box before making constraints.

Show Graph Switch Constraints

Show Graph Switch Constraints

The attached video should be sufficient to guide you through the showGraphSw constraints. Note that you MUST uncheck the Constrain to margins box before making constraints.

Connect Status Label Constraints

Connect Status Label Constraints

The attached video should be sufficient to guide you through the connectStatusLbl constraints. Note that you MUST uncheck the Constrain to margins box before making constraints.

With all the constraints for each object done, you are now ready to start interfacing these objects with the code!

Begin Swift Programming

Screen Shot 2021-02-14 at 6.55.14 PM.png
IB Outlet and Action

Open the ViewController.swift file. The main code you write will be in this file. First, let's import some important frameworks: CoreBluetooth and Charts. CoreBluetooth allows you to utilize objects and functions related to Bluetooth communication. The Charts module provides you with various objects and functions that allow you to visualize data nicely.

We will also begin initializations of some variables. They are listed in the screenshot. For each variable I've listed below, I recorded its data type and briefly described it. If you'd like a more in-depth explanation of CoreBluetooth, UUID, and how Bluetooth works in this context, Adafruit does a great job in their tutorial.

  • curPeripheral, CBPeripheral? - Current peripheral that was just found
  • txCharacteristic, CBCharacteristic? - TX (Transmit) characteristic found in the peripheral
  • rxCharacteristic, CBCharacteristic? - RX (Receive) characteristic found in the peripheral
  • centralManager, CBCentralManager! - Manager object that allows us to use Bluetooth related functions
  • rssiList, [NSNumber] - List of RSSI (Received Signal Strength Indicator) values from found peripherals
  • peripheralList, [CBPeripheral] - List of peripherals that were found
  • characteristicValue, [NSData] - Data object containing info of characteristic
  • timer, Timer() - Timer object used to make certain lines of code run at a specific time
  • BLE_Service_UUID, CBUUID - Constant Bluetooth UUID object containing UUID value specifically for Adafruit Feather nRF52832
  • BLE_Characteristic_uuid_Rx, CBUUID - Constant Bluetooth UUID object containing the necessary UUID value
  • BLE_Characteristic_uuid_Tx, CBUUID - Constant Bluetooth UUID object containing the necessary UUID value
  • receivedData, [Int] - Buffer array of integers holding data received from the peripheral
  • showGraphIsOn, Boolean - Whether or not the graph should be shown

Now, let's create the connections between the storyboard objects and your Swift code. Follow the video to get this set up. Once you do this, you will be able to refer to the storyboard objects in your swift code just by using their Outlet variable name or by calling the corresponding Action function. Note: The arrow.clockwise object at the top right corner of the screen works like a button only because I placed it in the Navigation Bar at the top. If you want to make a refresh button in the body of the Storyboard, use the actual Button object.

Connect Using BLE

Screen Shot 2021-03-14 at 4.13.38 PM.png
Screen Shot 2021-03-14 at 4.34.23 PM.png
Screen Shot 2021-03-14 at 4.34.43 PM.png
Screen Shot 2021-03-14 at 4.35.06 PM.png
Screen Shot 2021-03-14 at 4.35.36 PM.png
Screen Shot 2021-03-14 at 4.40.23 PM.png
Screen Shot 2021-03-14 at 4.40.39 PM.png
Screen Shot 2021-03-14 at 4.40.59 PM.png
Screen Shot 2021-03-14 at 4.49.46 PM.png
Screen Shot 2021-03-14 at 4.50.05 PM.png
Screen Shot 2021-03-14 at 4.50.27 PM.png

Now, here comes the coding part. You will basically be making a series of functions that will be needed to fulfill the app's roles. Certain events on the app, such as the user touching a button or peripherals getting connected, will trigger specific functions to run. Some functions may even trigger each other, depending on the circumstances. I've listed all the functions we'll be using below as well as a brief explanation of each one. Please refer to the attached images for guidance.

viewDidLoad() : This function is called before the storyboard view is loaded onto the screen. This is mainly used for setup activities. Here, we will set the label to say "Disconnected" and make the text red as its default setting. The CoreBluetooth Central Manager object is also initialize because it will be necessary to use CoreBlutooth functions.

viewDidAppear() : This function is called right after the view is loaded onto the screen. We will reset the peripheral connection with the app by disconnecting.

viewWillDisappear() : This function is called right before the view disappears from screen. In this case, we will have the Central Manager object stop the scanning for peripherals.

centralManagerDidUpdateState() : Called when the Central Manager's state is changed (it has 6 different states). This method is required for setting up Central Manager object. If manager's state is "poweredOn", that means Bluetooth has been enabled in the app. We can begin scanning for peripherals. Else, Bluetooth has NOT been enabled, so we display an alert message to the screen saying that Bluetooth needs to be enabled to use the app

startScan() : Start scanning for peripherals. Make an empty list of peripherals that were found. Stop the timer. Call method in centralManager class that actually begins the scanning. We are targeting services that have the same UUID value as the BLE_Service_UUID variable. Use a timer to wait 10 seconds before calling cancelScan().

cancelScan() : Cancel scanning for peripheral by calling the method in the Central Manager Object

disconnectFromDevice() : Disconnect app from peripherals

centralManager(didDiscover) : Called when a peripheral is found. The peripheral that was just found is stored in a variable and is added to a list of peripherals. Its rssi value is also added to a list. Connect to the peripheral if it exists / has services.

restoreCentralManager() : Restore the Central Manager delegate if something goes wrong.

centralManager(didConnect) : Called when app successfully connects with the peripheral. Use this method to set up the peripheral's delegate and discover its services. Stop scanning because we found the peripheral we want. Set up peripheral's delegate. Only look for services that matche our specified UUID.

centralManager(didFailToConnect) : Called when the central manager fails to connect to a peripheral.

centralManager(didDisconnectPeripheral) : Called when the central manager disconnects from the peripheral.

peripheral(didDiscoverServices) : Called when the correct peripheral's services are discovered. Check for any errors in discovery. Store the discovered services in a variable. If no services are there, return. For every service found... If service's UUID matches with our specified one... Search for the characteristics of the service

peripheral(didDiscoverCharacteristicsFor) : Called when the characteristics we specified are discovered. Check if there was an error. Store the discovered characteristics in a variable. If no characteristics, then return. Print to console for debugging purposes. For every characteristic found... If characteritstic's UUID matches with our specified one for Rx... Subscribe to the this particular characteristic. This will also call didUpdateNotificationStateForCharacteristic method automatically. If characteritstic's UUID matches with our specified one for Tx... Find descriptors for each characteristic. Called when peripheral.readValue(for: characteristic) is called. If characteristic is correct, read its value and save it to a string. Else, return.

peripheral(didWriteValueFor) : Called when the app wants to send a message to the peripheral.

peripheral(didDiscoverDescriptorsFor) : Called when descriptors for a characteristic are found. Store descriptors in a variable. Return if nonexistent. For every descriptor, print its description for debugging purposes.

Feel free to copy the code from my ViewController.swift.

Hopefully, at this point, you are able to connect your app to the Adafruit Feather. You can test this by turning on the Feather by plugging it into the computer and running the app. If all goes well, the connectStatusLbl on the top right corner of the screen should turn blue and say "Connected!" and the app will start graphing the data that the Feather has sent.

Using Charts to Graph Data

Screen Shot 2021-03-14 at 4.50.41 PM.png

Now that we can connect our app with the Feather via BLE, we are ready to do some graphing. The graphing functions and objects here come from the Charts module that we took some time to set up earlier. For our purposes, we will be using functions and objects related to LineChart only. These include ChartDataEntry, LineChartDataSet, and LineChartData. I've explained the functions we used below as well as in the comments in the attached image. Some other examples of these functions being used can be found here: (https://github.com/danielgindi/Charts/tree/master/... )

displayGraph() : Graph the dataset to storyboard. Array that will eventually be displayed on the graph. For every element in given dataset. Set the X and Y status in a data chart entry and add to the entry object. Convert lineChartEntry to a LineChartDataSet. Customize graph settings to your liking. Make objects that will be added to the chart and set it to the variable in the Storyboard. Settings for the chartBox.

clearGraph() : Clear the graph by displaying a dataset with no elements.

Charts Objects

ChartDataEntry: Object containing the (x, y) coordinate for the data entry.

LineChartDataSet: A set of "ChartDataEntry" points grouped together with a label and other uniform settings, such as marker style, marker color, marker size, etc.

LineChartData: Contains a LineChartDataSet object.

Again, feel free to copy the code from my ViewController.swift.

Once you get all the functions done, try plugging in the Feather (make sure the code from Step 2 is uploaded) and run this iOS app on your phone. You should see that the Feather connects to the app and starts sending its data. The app will receive the data and display it as a line chart.

Conclusion

I enjoyed making this project and the Instructable and I hope you were able to follow along and enjoy it as well! For credits and resource purposes, this Instructable was inspired by Adafruit's tutorial.