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.

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:

—–image missing!

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:

For privacy reasons YouTube needs your permission to be loaded. I Accept

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