BLEBot – nRF51822 based BLE Robot

Introduction
In this project, we will build a two-wheeled robot based on the Nordic nRF51822 BLE SoC. Motor control for the two wheels of the robot will be done using an L293D chip. The robot can be controlled using any mobile device that has BLE, and when not connected, it switches to an autonomous mode, avoiding obstacles using an HC-SR04 ultrasonic sensor.
Background
Before you read further, you might want to look through some of my previous articles on nRF51822 programming, since we’re going to use similar concepts and development setup here.
- nRF51822 Begins – nRF-DK, GCC, ADC, UART/BLE
- External nRF51822 SWD Programming using the nRF51-DK
- nRF51-DK PWM & GPIOTE test with S110 SoftDevice
- Talking to Ultrasonic Distance Sensor HC-SR04 using nRF51822
- Controlling an RGB LED with Nordic nRF51-DK (nRF51822/nRF51422)
- Motor Control over BLE with nRF51822 and TB6612FNG
Hardware & Connections
Here’s the list of hardware you need for this project:
- An nRF51822 Module.
- An nRF51822 Module Adapter (optional).
- L293D motor driver IC.
- Two wheel robot chassis with two motors.
- HC-SR05 ultrasonic sensor.
- A regulator IC like LM7805.
- A LiPo battery or similar which can supply enough voltage and current for the motors and the module.
The type of nRF51822 module I used is made in China, and available at sites like aliexpress, and they come with two 2×9 2 mm pitch headers. These are awfully hard to use, so I (in collaboration with my friend Sandeep) designed an adapter to make them breadboard friendly. These are optional for this project, but they are available for purchase on Tindie if you would like to check them out. In addition to making the module easy to use, our adapter also has a built-in LED and a voltage regulator.
Here’s how I hooked up the L293D and the nRF51822. (These connections are consistent with my code. If you do them differently, update the code to match.)
nRF51822 | L293D | Other |
---|---|---|
P0.01 | 1 | N/A |
P0.02 | 2 | N/A |
P0.03 | 7 | N/A |
P0.04 | 9 | N/A |
P0.05 | 15 | N/A |
P0.06 | 10 | N/A |
P0.07 | N/A | HC-SR04 Trig |
P0.23 | N/A | HC-SR04 Echo via resistor divider |
P0.21 | N/A | LED |
GND | N/A | GND |
VDD | N/A | 3.3 V |
N/A | 4, 5, 12, 13 | GND |
N/A | 3, 6 | motor#1 |
N/A | 11, 14 | motor#2 |
N/A | 8, 16 | VCC |
Note above that HC-SR05 runs on 5V, and nRF51822 on 3.3 V. So you need to ensure that the Echo signal from HC-SR05 is reduced from 5V TTL to 3.3V TTL, and the simplest way to do that is using a resistor divider, as explained in my previous article on using HC-SR04 with nRF51822.
For power supply, I used 4 x 1.5 V alkaline batteries. I supplied 5V to the L293D using a 7805 regulator, and since my nRF51822 adapter already has a built in regulator (and an LED), so I powered it separately from a 9V battery. I recommend that you use a good capacity 7.4 V LiPo battery to power this project.
nRF51822 Chip Versions & SDK
You need to ensure that the chip version, the Nordic nRF51 SDK, and the SoftDevice versions are compatible. The two documents you need to check this are the Product Anomaly Notice and the nRF51 Series Compatibility Matrix – both available from Nordic website or a web search.
From these documents, here’s how you identify the chip:

In my case, the chip on the module says QFACA2 and 1513AN. So it has 256 kB flash, 32 kB RAM, and it was made in 2015. This information is very important, and the ld file needs to be consistent with the chip. Here are the contents of my ld file:
/* Linker script to configure memory regions. */
SEARCH_DIR(.)
GROUP(-lgcc -lc -lnosys)
MEMORY
{
FLASH (rx) : ORIGIN = 0x18000, LENGTH = 0x28000
RAM (rwx) : ORIGIN = 0x20002000, LENGTH = 0x6000
}
INCLUDE "gcc_nrf51_common.ld"
Here’s how you identify the chip revisions:

And here’s a graphic that shows the SDK compatibility:

In my case, I am using the Nordic nRF51 SDK version 8.1.0 for this project.
The Code
I’ll go through the highlights of the code here, but you’ll need to go through the github link in Downloads below for the full picture. In addition to the Nordic SDK files, the code for BLEBot is organized in these files:
- main.c
- ble_int.h
- ble_init.c
- distance.h
- distance.c
The Main Loop
Here is the main loop of BLEBot:
while(1) {
if(is_connected()) {
// stop if coming from an unconnected state
if(!prevStateConnected) {
stop();
}
// execute command if any
if(bbEvent.pending) {
handle_bbevent(&bbEvent);
}
// flash LED twice quick
nrf_gpio_pin_set(pinLED);
nrf_delay_ms(100);
nrf_gpio_pin_clear(pinLED);
nrf_delay_ms(100);
nrf_gpio_pin_set(pinLED);
nrf_delay_ms(100);
nrf_gpio_pin_clear(pinLED);
nrf_delay_ms(100);
prevStateConnected = true;
}
else {
// start moving if previous state was connected
if (prevStateConnected) {
set_dir(true);
set_speed(0, 80);
set_speed(1, 80);
}
// move robot autonomously
auto_move();
// flash LED once
nrf_gpio_pin_set(pinLED);
nrf_delay_ms(500);
nrf_gpio_pin_clear(pinLED);
nrf_delay_ms(500);
prevStateConnected = false;
}
}
In the above loop, if the BLE connection is active, the robot follows instructions sent via the Nordic UART Service (NUS) from the mobile device. In this mode, it moves in reponse to left, right, stop, reverse, and start. I use the Nordic nRFToolBox app for using NUS. I also flash the LED connected to P0.21 twice quickly in each iteration of the loop.
Here’s the NUS data handler that takes action based on data that comes in via BLE.
// Function for handling the data from the Nordic UART Service.
static void nus_data_handler(ble_nus_t * p_nus, uint8_t * p_data,
uint16_t length)
{
// clear events
bbEvent.pending = false;
if (strstr((char*)(p_data), REWIND)) {
bbEvent.pending = true;
bbEvent.event = eBBEvent_Left;
}
else if (strstr((char*)(p_data), FORWARD)) {
bbEvent.pending = true;
bbEvent.event = eBBEvent_Right;
}
else if (strstr((char*)(p_data), STOP)) {
bbEvent.pending = true;
bbEvent.event = eBBEvent_Stop;
}
else if (strstr((char*)(p_data), PLAY)) {
bbEvent.pending = true;
bbEvent.event = eBBEvent_Start;
bbEvent.data = 80;
}
else if (strstr((char*)(p_data), SHUFFLE)) {
bbEvent.pending = true;
bbEvent.event = eBBEvent_Reverse;
}
}
In the above code, the motor states are changed based on the commands that come in. (These strings are tailored to the Nordic nRF Toolbox app, but you can change them to suite your mobile app.) It’s not a good idea to do any heavy lifting from BLE event callbacks, so all I do when I get an event is set a pending flag and the event type. The actual handling is done in the main loop, as you saw before.
Here is the data structure used for events:
// events
typedef enum _BBEventType {
eBBEvent_Start,
eBBEvent_Stop,
eBBEvent_Reverse,
eBBEvent_Left,
eBBEvent_Right,
} BBEventType;
// structure handle pending events
typedef struct _BBEvent
{
bool pending;
BBEventType event;
int data;
} BBEvent;
BBEvent bbEvent;
The idea is that you can set a one-shot pending flag and the event type, which can be checked in the main loop and handled.
Here is the handler code for motion control.
// handle event
void handle_bbevent(BBEvent* bbEvent)
{
switch(bbEvent->event) {
case eBBEvent_Start:
{
set_dir(curr_dir);
set_speed(0, bbEvent->data);
set_speed(1, bbEvent->data);
}
break;
case eBBEvent_Stop:
{
stop();
}
break;
case eBBEvent_Left:
{
turn(false, 500);
}
break;
case eBBEvent_Right:
{
turn(true, 500);
}
break;
case eBBEvent_Reverse:
{
set_dir(!curr_dir);
}
break;
default:
break;
}
// clear
bbEvent->pending = false;
}
The motion of the motors is controlled by sending the correct signal to the L293D (The D indicates that the IC has built-in flyback diodes.), which is configured as a dual H-bridge driver, controlling both the direction and speed of two connected motors. The speed is controlled by changing the PWM duty cycle on the A pins, and the direction of rotation is by setting the EN pins to HIGH or LOW. For example, this is how you set the direction of motion:
void set_dir(bool forward)
{
if(forward) {
// set direction A
nrf_gpio_pin_set(pinIN1);
nrf_gpio_pin_clear(pinIN2);
// set direction B
nrf_gpio_pin_set(pinIN3);
nrf_gpio_pin_clear(pinIN4);
}
else {
// set direction A
nrf_gpio_pin_clear(pinIN1);
nrf_gpio_pin_set(pinIN2);
// set direction B
nrf_gpio_pin_clear(pinIN3);
nrf_gpio_pin_set(pinIN4);
}
curr_dir = forward;
}
Above, we use the GPIO pins to set the direction of the motors. Here’s how you set the motor speeds:
void set_speed(int motor, uint8_t speed)
{
// error check
if (motor < 0 || motor > 1)
return;
// set speed
while (app_pwm_channel_duty_set(&PWM1, motor, speed) == NRF_ERROR_BUSY);
motor_speeds[motor] = speed;
stopped = false;
}
To set speed, you just set the PWM duty cycle. And here’s how you turn:
void turn(bool left, int tms)
{
if(left) {
// stop motor 0
int tmp = motor_speeds[0];
set_speed(0, 0);
set_speed(1, 50);
// wait
nrf_delay_ms(tms);
// reset
set_speed(0, tmp);
set_speed(1, tmp);
}
else {
// stop motor 1
int tmp = motor_speeds[1];
set_speed(1, 0);
set_speed(0, 50);
// wait
nrf_delay_ms(tms);
// reset
set_speed(0, tmp);
set_speed(1, tmp);
}
}
To turn, you just reduce the speed of one motor for the specified time in milliseconds. And here’s how you stop:
void stop()
{
// set direction A
nrf_gpio_pin_set(pinIN1);
nrf_gpio_pin_set(pinIN2);
// set direction B
nrf_gpio_pin_set(pinIN3);
nrf_gpio_pin_set(pinIN4);
stopped = true;
}
The above methods are all you need to drive the robot around.
If BLE connection is inactive, the robot goes into an autonomous mode using the HC-SR04 ultrasonic sensor to detect obstacles. It uses a very simple algorithm: if it detects an object at a distance less than a certain threshold, it stops, reverses, turns left, and continues on. In this mode, I also flash the LED slower, so it’s easy to see whether the robot is connected via BLE or not.
Here’s the simple logic that is used to move the robot autonomously.
void auto_move()
{
// get HC-SR04 distance
float dist = 1.0;
if(getDistance(&dist)) {
// obstacle avoidance
if (dist < 15) {
// stop
stop();
// reverse
set_dir(false);
set_speed(0, 50);
set_speed(1, 50);
nrf_delay_ms(1000);
// turn left
turn(true, 500);
// go
set_dir(true);
set_speed(0, 80);
set_speed(1, 80);
}
}
}
In the above code, if the distance from the ultrasonic sensor is less than a certain threshold, we stop the motors, reverse, turn, and then continue on forward. Nothing too fancy, but it does work. I have written previously on interfacing the nRF51822 with the HC-SR04 ultrasonic sensor, so I won’t repeat it here. The distance computation code is in distance.c.
Uploading Code to the nRF51822
To program the nRF51822 module, you need to use an SWD programmer. I can’t go into details here, but if you have the Nordic nRF51-DK, you can read my article on nRF51-DK external SWD programming to get started. You can use other tools to do the job also. For example, here’s an nRF51822 project that uses an st-link programmer.
In Action
Here’s BLeBot in action:
And here’s a view of some of the signals in BLEBot (Saleae Logic 8).

I think it’s quite impressive that this tiny Nordic SoC can handle an ultrasonic sensor, control two motors, blink an LED, and handle BLE communications at the same time.
Downloads
You can get the complete source code for this project here:
https://github.com/electronut/blebot