In this post I will explain how to use the HM10 Bluetooth UART module to connect an iPhone app to an Arduino over Bluetooth 4.0 LE. This is as far as I know the easiest and most affordable way to have an iOS app and an Arduino interact with each other.

The other options are:

  • The Arduino 101 board (expensive and chunky)
  • Using WiFi (more complicated and requires WiFi, obviously)
  • The BLEduino (not available any more)
  • Redbearlabs BLE shields (too expensive IMHO)
  • Using a RS232 adaptor ( expensive & requires Apple MFI program membership to distribute)
  • Update: The ESP32! Finally an affordable, programmable, BLE/WiFi board for the masses! However, for many projects the HM10 still remains the cheapest and easiest way to add BLE (because it does not need to be programmed).

In case you don’t know, the HM10 is a small chip made by JNHuaMao that uses a Texas Instruments CC2540 (or CC2541) BLE chip to function as a serial pass trough. Like the Arduino it has a TX and a RX pin: data it receives over serial will be sent trough Bluetooth to the iDevice, and vice versa. The HM10 has more capabillities, like being able to act as an iBeacon or as a central device, which we won’t get into.

I expect that you already have some experience with programming Arduino’s and iPhone apps. I’ll explain what needs to be done and provide sample code, but I won’t give you step-by-step instructions.

It is neccesary to have a basic understanding of BLE as well. I recommend watching ‘Core Bluetooth 101’ from WWDC 2012.

Update: I made ‘lite’ and ‘pro’ Bluetooth serial apps for iOS, however I have quit iOS development and my apps have been removed from the App Store. Someone else has updated them to the current iOS version and re-uploaded them: Lite versionPro version. You can also view the source code on GitHub: Lite versionPro version.

Step 1: Get the Hardware

HM10’s are available on eBay for just $2 including shipping. But keep in mind that the HM10 runs off 2.0 – 3.7 Volts, while most Arduino’s use 5V. This can be solved with a simple voltage divider, or – for a few bucks extra – you can get a module already soldered onto a regulator like this one (or get a HM16).

The HM11 is basically the HM10 but in a smaller package. Also, not all of the CC2540’s pins are exposed with the HM11. But we won’t be needing those anyway, so you can also get a HM11 if you prefer.

Since I wrote the first version of this tutorial JNHuaMao has released newer versions of the HM10 and HM11 with new 32bit chips: the HM16 and HM17. Improvements include bluetooth 4.1 compatibility and 5-volt tolerance. The HM18 and HM19 support bluetooth 4.2.

A few more points:

  • A standalone HM10 will need to be soldered, for which you need a soldering iron with a very fine tip.
  • And apart from an Arduino and some wires, you could optionally add a LED with a 470Ω resistor, and a button with a 1kΩ resistor.
  • If you don’t have any Arduino’s laying around, you’ll also need an USB to Serial adaptor.

The first HM10 I ordered turned out to a fake one from Bolutek. While they will function alright as a serial passtrough, they have fewer features and AT commands. Also the format of the AT commands is different (which we’ll get to in a sec). The main difference in hardware is the missing 32kHz external oscillator (the two solder pads in the top left of the middle board). While the original HM10 firmware seems to work fine on fake boards (I will cover flashing HM10s in a later tutorial), other CC2541 firmwares like BlueBasic do require an external oscillator. I recommend getting a genuine HM10, nowadays they cost the same as fakes anyway.


Step 2: Connecting the Hardware

You’ll need to solder wires to the following pins (see diagram below)

  1. The 2nd lowest pin on the left side (the +)
  2. The lowest pin on the left side (the -)
  3. The top pin on the left side (the TX pin)
  4. The pin below the TX pin (the RX pin)

Voltage dividers

If you are going to use it with a 5V device, a voltage divider is needed for the RX pin and + pin. If there is a 3.3V power supply available (like on most Arduino boards) you can obviously leave out the voltage divider on the + pin. Though in my experience you only need a divider for the + pin, my HM10s have handled 5V signals on the RX pin just fine.

System LED

It can be usefull to be able to see wether the module is powered and whether it is connected. So if you want you can connect a LED and a 470Ω resistor in series between the LED pin of the HM10, and GND. This LED will flash once a second when turned on, and stay on if it is connected to a BLE device.

System KEY

Optionally you can connect a button to the 2nd lowest pin on the left side of the board, as seen on the diagram.

If you push this button for longer than 1 second, the module will:

  • If it is in sleep mode, wake up immediately
  • If it is connected via bluetooth, disconnect
  • If it is in standby mode, reset to factory default settings (not tested) and restart

Step 3: Configuring the HM10

Next we are going to configre the HM10. Though if you are okay with the default settings, you can skip this step.

The HM10 is configured by sending AT commands to it using its physical serial connection. Connect your bluetooth module to a USB to Serial converter* (+ to +, – to -, TX to RX and RX to TX). If you don’t have one, you can also use an Arduino board with the example SoftwareSerial sketch loaded. Optionally, if you change the mode of the HM10, it can also accept AT commands over bluetooth (with some restrictions).

Next connect the USB-Serial converter to your computer and open the serial monitor of the Arduino IDE (first of course select the appropiate serial port in Tools > Serial Port). The default baud rate of the HM10 is 9600, so make sure you have that selected. Make also sure you have selected ‘No line ending‘. **

Type AT (uppercase, no spaces) and hit enter. If all is well, the HM10 should immediatly respond with OK.

Now that we’ve verified that it’s working, we can change the name of the module: To change the name to ‘Bluetooth Serial’, type AT+NAMEBluetooth Serial and hit enter, it should respond with something like OK+Set:Bluetooth Serial. To get the current name, type AT+NAME?.

We can do much more with AT commands, most importantly changing the passcode and the baudrate. A complete list of all AT commands can be found in the datasheet.

* Note: I do not recommend buying a FT232R based USB-Serial converter, unless you are absolutely sure it is a legit one. If it tuns out to be fake, it is going to be useless.

** Note: If your HM10 is a fake (like mine turned out to be) the AT commands available will be different, and it might require a carriage return + line ending (\r\n) at the end of every command. Here are the AT commands of the Bolutek CC41.

Changing the Baudrate

Use AT+BAUDx to change the baudrate, x being the desired option from the table below. Note that when you change the baudrate, it will not respond any more as the serial monitor will also have to change its baudrate.

OptionBaud rate

Changing the Passcode

By default the HM10 will not ask for a passcode when connecting. We can change this with the AT+TYPE command. It has the following options:

  • AT+TYPE0: Do not require passcode, don’t pair (default)
  • AT+TYPE1: Do not require passcode, but do pair*
  • AT+TYPE2: Require passcode, don’t pair
  • AT+TYPE3: Require passcode and pair**

The manual notes that this command should not be used in versions older than V515 (use AT+VERR? to find out the version) and that under Android 4.3, AT+TYPE1 is the same as AT+TYPE2. AT+TYPE3 is added in V524. I have a tutorial here on updating the HM10 firmware on a Mac.

Now that the connection type has been set, we can update the passcode using AT+PASSx, x being a six-digit number. On older versions of the firmware you might have to use AT+PINx instead. To get the current passcode use AT+PASS?.

To forget a paired device, use AT+ERASE followed by AT+RESET to restart it. Important: After doing this the iOS device will still think it is paired with the device, which can result in some strange behaviour. You must “Forget” this device manually in the iOS System Preferences too!

* Note: I have no idea what the purpose of this mode is. All this does (as far as I know) is it triggers a pairing request alert every time you try to connect. The iOS device does not actually remember the pairing like with AT+TYPE3.

** Note: Bytes sent from iOS immediately after pairing do not seem to be transmitted properly.

Step 4: Test123…

Now it’s time to test wether everything works as expected. Keep your HM10 connected to your computer and download my Bluetooth Serial test app on the app store (see above for links). Open the app and connect to the HM10. Once it has been connected, the status LED will stop blinking and stay on.

Text you type in the app will now show up in the serial monitor, and text you type in the serial monitor will appear in the app.

You can also use the pro version, which has a tonne of extra features that will help you debug HM10 applications (again, see above).

Step 5: Writing the Arduino Sketch

The Arduino sketch really is like any other sketch that sends and receives data over serial. Have a look at the Serial and SoftwareSerial documentation. The connections are simply:

  • HM10 +   to     Arduino 3.3V
  • HM10 –   to    Arduino GND
  • HM10 TX to   Arduino RX, or SoftwareSerial’s RX pin
  • HM10 RX to   Arduino TX  or SoftwareSerial’s TX pin (either way with the voltage divider inbetween)

Here is a sketch written by someone else (Ladvien) that used the HM10. Another example would be my Stein;Way project.

I’m not going to provide a sample sketch as your application won’t be anything like blinking a LED anyway (I presume?), but if you need help just leave a comment below.

Step 6: Writing the iOS App

I won’t explain every single step of writing the iOS app either – I’m more a fan of learning by studying sample code. I have published the code of my HM10 test app on GitHub. Other examples are my Eaze and Bluetooth MIDI iOS apps.

And maybe you don’t need to code at all! HM10 Bluetooth Serial Pro supports creating buttons that send custom, pre-set messages. That could be all you need. (See the update in the introduction for links).

Before you start

The HM10 has one service, 0xFFE0, which has one characteristic, 0xFFE1 (these UUIDs can be changed with AT commands by the way). This service is both the one you have to read and write to/from. Long messages will be broken into smaller parts – keep this in mind when deciding on your protocol.

Use a helper class?

You don’t even have to write your own implementation – you can just use my BluetoothSerial class. It will do all of the Core Bluetooth stuff for you. First initialize it with func init(delegate:), then use scanForPeripherals to start scanning. You’ll have to provide an UI yourself that will list all peripherals that are reported to the delegate – using serialDidDiscoverPeripheral – so the user can choose one. Connect to it with connectToPeripheral, and the SerialHandler will notify you when it is ready with serialIsReady.

Now you can communicate with the connected peripheral using various functions/delegate functions. You can choose to send and/or receive either strings, bytes, or data objects.

I suggest that you have one global instance of this class, of which the delegate is always set to the current view that is using it.

Note 1: every delegate of the BluetoothSerial should implement the serialDidChangeState delegate method, in case bluetooth is turned off. And when de HM10 is connected, all delegates should also implement serialDidDisconnect in case the HM10 is turned off. Common programmers sense applies here.

Note 2: You’ll still need to put import CoreBluetooth in every file you use BluetoothSerial, as some of the CB classes are still going to be used (like CBPeripheral and CBCentralManagerState)..

Note 3: Be sure to set  writeWithResponse correctly. Explanation is available in the source code. WriteType is set automatically.

Writing you own implementation

Have a look at the source code of BluetoothSerial, you’ll see connecting to a HM10 works like this:

  1. Initialise an CBCentralManager instance and set its delegate
  2. Scan for advertising peripherals centralManager.scanForPeripheralsWithServices([CBUUID(string: "FFE0")], options: nil)
  3. Find all already connected peripherals centralManager.retrieveConnectedPeripheralsWithServices([CBUUID(string: "FFE0")])
  4. Connect to the peripheral chosen by the user
  5. Once connected, set the peripheral’s delegate
  6. Start looking for its 0xFFE0 service
  7. Once it has been found, look for the service’s 0xFFE1 characteristic
  8. Once it has been found, create a reference to it
  9. Determine the WriteType writeType = ? .withResponse : .withoutResponse
  10. And subscribe to it peripheral.setNotifyValue(true, forCharacteristic: characteristic)

Now we’re ready to communicate with the HM10. According to Apple’s Best Practices, there is a third way to retreive peripherals. However, I have not included it, beceause in my view it is not needed.

Peripherals that have been disconnected from the iDevice can still stay connected for up to 50 seconds, a ‘feature’ described here. So just in case the user tries to reconnect within 50 seconds after disconnecting, we have to search for all already connected devices as well (step 3).

We can write data to the module by calling connectedPeripheral!.writeValue(data:, forCharacteristic:, type:) with the data you want to send, the characteristic we made a reference to earlier, and finally the writetype that we also determined while connecting.

This is very important! When writing data over BLE, legit HM10 modules require writing without response while fake HM10’s may require writing with response. However, we don’t need to worry about this, because we can simple look at the to know what WriteType we need to use.

We can receive data by implementing the CBPeripheralDelegate method peripheral(peripheral:, didUpdateValueForCharacteristic characteristic:, error:) This will get called because we subscribed to the characteristic earlier on, remember?

Of course we send and receive Data objects, so we have to convert the String:

// data to string
let str = String(data: characteristic.value!, encoding: .utf8)

// string to data .utf8)

Note: Don’t forget to have a look at the documentation of CBCentralManagerDelegate and CBPeripheralDelegate, there are of course more functions you’ll need to implement to properly handle unexpected changes.

Example Code

More Resources

Wrapping up

Once the HM10 is configured and the CoreBluetooth stuff is taken care off, the communication really isn’t that hard, it is in fact like any other serial communication.

I hope that I have explained enough for you to write your own code, but if you have any questions or need help with your project, just leave a comment blow.

If my tutorial and sample code have helped you, please consider getting the HM10 Bluetooth Serial Pro app. It has a tonne of useful features for debugging HM10 projects and it allows you to create buttons for pre-set messages (possibly removing the need for you to code entirely!).

Update 18/07/22: Added info on removal/reupload of apps on App Store.

Update 17/01/19: If you have experience with Ionic/Cordova check out robofred’s SPP over BLE project. He is looking to create an easy to use cross-platform solution for BLE serial communications and is currently searching for programmers who could help him achieve this.

Edit 03/04/18: Added info about ESP32 and added 2nd note to the part about pairing.

Edit 16/02/18: Updated part about AT commands with more information about the pairing behaviour.

Edit 07/10/17: Small improvements.

Edit 18/04/17: Changes all around, updated code, updated links, plus info about HM10 Bluetooth Serial Pro.

Edit 13/04/16: Some major changes in the code I provided. Nothing functional, just better/nicer/cleaner code. I also added more information, and made the article better readable.

Edit 14/10/15: I’ve added some info’s about the difference in writing data to fake or legit HM10’s.