Showing posts with label control. Show all posts
Showing posts with label control. Show all posts

Wednesday, August 8, 2018

Espressif ESP32 Tutorial - IR Remote Control using Microsoft Azure




The Project


This tutorial will outline how to create an IR Remote using the ESP32 and then control it from the IoT hub on Microsoft Azure.

Driving an IR remote transmitter using an Arduino is simple, as there is a library, called IRremote.h which does all the hard work. You just need to connect your IR transmitter module signal pin to the appropriate Arduino pin, via a current limiting resistor and you are done. Connecting an Arduino to the cloud takes a bit more work (depending on the model you are using), which is why we wanted to use the ESP32.

Unfortunately, the standard IRRemote.h Arduino library only supports receiving IR signals on the ESP32 not transmitting them. Fortunately, Andreas Spiess has forked the standard library and added ESP32 transmission capability. You will need to download the ESP32-IRremote library, so we can use it with the ESP32. Andreas did this by using ledC PWM. You can now select any pin to use with IRsend(pin). Note this is only for the ESP32, the other board types have defined pins you have to use due to the assigned timers.

The Duinotech Infrared Transmitter Module


The IR transmitting module which I used is the one from Jaycar (branded Duinotech). There is a data sheet available on the Jaycar site but it is fairly sparse and doesn't clearly define the pins on the module.

It appears that this module is based on the KY-005 INFRARED TRANSMITTER MODULE. The specifications for which are:


Operating Voltage 5V
Forward Current 20 ~ 60 mA
Power Consumption 90mW
Operating Temperature -25°C to 80°C [-13°F to 176°F]
Dimensions  18.5mm x 15mm [0.728in x 0.591in]

This being the case, the pin out is as follows:



The signal pin is clearly labeled with an S, the middle pin is GND via a resistor (* which you have to fit yourself to the module) and GND is connected to the third pin (with the "-" adjacent to it). This module is just an infrared diode (which emits at a wavelength of 940 nm).

Thus, we can drive it like any other diode via a current limiting resistor. The value of the resistor depends on what voltage your micro controller digital outputs (DO) are switching, the desired diode forward current and the forward voltage drop characteristic of the diode. So for our design:

VDO = 3.3V
If = 20 mA
Vf = 1.2V (nominally 1.1V but I measured this using my LCR meter)

Then, R = (VDO - Vf) / If
              = (3.3 - 1.2) / 0.02
              = 105 Ω



We will use a 100 Ω resistor in our circuit.



The CIR (Commercial Infrared) Transmission Protocol


As there are usually other sources of infrared radiation (e.g. sunlight and incandescent or LED lights), the 940nm IR transmitter is modulated by a carrier frequency in the 32-40 kHz range. CIR receivers incorporate a bandpass filter tuned to this carrier frequency. This allows the receiver to discriminate between the modulated IR signal and any ambient, unmodulated IR. In effect, the IR receiver is double tuned both to the wavelength of the IR radiation and to the carrier frequency.



Three factors influence CIR range. In order of decreasing importance they are: the power level of the IR emitter, the IR wavelength, and the carrier frequency. An IR emitter's output is proportional to the current through the emitter. Increasing the current will increase the power. Because the duty cycle is usually 50% or less, the emitter can be driven with quite high currents. For optimal range, the IR wavelength of the emitter and receiver should match.



A similar protocol to CIR is IrDA. IrDA was popular in the late 1990's but has largely been replaced by Bluetooth and WiFi. IrDA was designed to be very short range (< 1 m). It does not use any secondary carrier but directly modulates the 850nm IR with the data. Because of this, it is susceptible to interference from ambient IR. In addition, the IrDA transmitter is usually lower power than a CIR transmitter.

An IrDA transmitter with a CIR receiver is a mismatch as is a CIR transmitter with a IrDA receiver. They operate on a different wavelength (940 nm vs 850 nm), IrDA isn't modulated, and the beam angles are different (IrDA limits the beam angle to ±15° while most CIR emitters are ±40° or greater). Such mismatches have major effects on range and reliability.

We will use CIR in our design.

For RF control, both the transmitter and receiver need to be tuned to the same carrier frequency and need to use the same type of modulation. Most RF remotes use ASK (Amplitude Shift Keying) or OOK (On-Off Keying). OOK is really just a special case of ASK. OOK is also called CPCA (Carrier Present, Carrier Absent). You can have a look at the IRremote library to see how this coding is achieved.

Microsoft Azure




Azure is Microsoft's catch all name for their cloud services. It covers over 100 different services. The service of interest to us is IoT Hub. You can use Azure IoT Hub to securely connect, monitor and manage billions of devices to develop Internet of Things (IoT) applications. To get started we will connect just one device!

You will need to sign up for a free Azure account. Follow the link above and do this. For some reason Microsoft make you provide credit card details, even for the free account. Note that when you sign up, the email address you provide becomes the name of the default active directory (which wouldn't be my first preference).

It will be interesting comparing the Microsoft IoT hub functionality with node-red, which is another dashboard option that we have had experience with. At this stage I suspect that node-red is much cheaper (free) and simpler but Azure is more robust, secure and scalable. The key features of IoT hub are:
  1. Bidirectional communication with LOTS of devices. Use device-to-cloud telemetry data to understand the state of your devices and define message routes to other Azure services without writing any code. In cloud-to-device messages, reliably send commands and notifications to your connected devices – and track message delivery with acknowledgement receipts. Device messages are sent in a durable way to accommodate intermittently connected devices.
  2. Authentication per device. Set up individual identities and credentials for each of your connected devices, and help retain the confidentiality of both cloud-to-device and device-to-cloud messages. To maintain the integrity of your system, selectively revoke access rights for specific devices as needed.
  3. Automated device registration. Speed up your IoT deployment by registering and provisioning devices with zero touch in a secure and scalable way. IoT Hub Device Provisioning Service supports any type of IoT device compatible with IoT Hub.
  4. Use IoT Edge. Take advantage of IoT Edge to make hybrid cloud and edge solutions. IoT Edge provides orchestration between code and services so they flow securely between cloud and edge to distribute intelligence across a range of devices. Enable artificial intelligence and other advanced analytics at the edge.
Once you have signed up for Azure, you will be presented with a dashboard similar to that shown above.

Create an IoT Hub




Microsoft call their menus "blades" in Azure. No idea why, maybe because it sounds cooler than menu? Anyway, click on the + Create a resource link on the blade to the left of the dashboard. This will open the Azure Marketplace.

In the Marketplace, click on Internet of Things. This will provide a new list of menu options to the right.


We want IoT Hub at the top. Click on this to setup your hub. For subscription select Free Trial and for Resource Group, Create new.

The free tier is intended for testing and evaluation. It allows 500 devices to be connected to the IoT hub and up to 8,000 messages per day. Each Azure subscription can create one IoT Hub in the free tier.

A resource group is a container that holds related resources for an Azure solution. The resource group can include all the resources for the solution, or only those resources that you want to manage as a group.

Select the Region closest to your location. In Australia the options are East and Southeast, which I think refer to Sydney and Melbourne respectively.

To create an IoT hub, you must name the IoT hub. This name must be unique across all IoT hubs. The IoT hub will be publicly discoverable as a DNS endpoint, so make sure to avoid any sensitive information while naming it. Once created, the name can't be changed.

Click the button at the bottom labelled - Next: Size and Scale >>



For pricing and scale tier, select F1: Free tier. That is all that you can adjust on this screen. Click Review + create.

When all previous steps are complete, you can create the IoT hub. Click Create to start the back-end process to create and deploy the IoT hub with the options you chose.

It can take a few minutes to create the IoT hub as it takes time for the back-end deployment to run on the appropriate location servers. Once your new IoT resource has been created, you can customise your dashboard.


Add an IoT Device



Before a device or module can connect to your IoT hub, there must be an entry for that device in the IoT hub's identity registry. A device must also authenticate with the IoT hub based on credentials stored in the identity registry. The device or module ID stored in the identity registry is case-sensitive.

To add a new IoT device, click on + Add, and the Add Device blade will be displayed.

  • Device ID: A case-sensitive string (up to 128 characters long) of ASCII 7-bit alphanumeric characters.
  • Authentication Type: Symmetric Key or X.509 Certificate. I used Symmetric Key. The differences are:
    • Symmetric Key: a unique identity key (security tokens) for each device, which can be used by the device to communicate with the IoT Hub.
    • X.509 Certificate: uses an on-device X.509 certificate and private key as a means to authenticate the device to the IoT Hub. This authentication method ensures that the private key on the device is not known outside the device at any time, providing a higher level of security.
  • Auto Generate Keys: tick.
  • Connect device to IoT hub: enable.

Click on Save, and your new device will be added to the hub. Click on the device ID of the newly added device to see the security keys and connection strings. You will need the device ID and a copy of the primary connection string for insertion into your ESP32 sketch. Now onto the ESP32.

ESP32 Software


You can download a copy of my ESP32 sketch from the Reefwing Gist.  You will need to fill in your SSID, password and primary connection string where indicated.



I spent quite a bit of time trying different tool chains to get everything configured and talking. My initial preference was to use Eclipse with the Arduino tool chain. This would give me a proper IDE and a remote control library that I knew was compatible with all the Arduino's out there.

Unfortunately, importing custom libraries is a bit problematic for the two Eclipse Arduino plug-ins available. It is theoretically possible but I ran out of patience trying to get it to work. The ESP-IDF has its own remote control library but it is not as well documented as the Arduino library. I'm also not familiar with coding the ESP32's natively.



While looking for a way to receive the messages sent from the ESP32 to the cloud I discovered a plug in for Visual Studio Code. It so happens that there is also a plug in for Arduino. Since I was already using this to monitor my IoT hub traffic, I decided to give it a crack with programming the ESP32. It just worked - I was astonished! It does use the Arduino IDE tool chain, so that may be why it was so seamless as I had already got everything working with that first. If IntelliSense complains about a missing library, right click on the light build and edit the c_cpp_properties.json file which contains the include path.




The other thing you will probably have to do is add:

"output": "../build",

To the .vscode/arduino.json file, which can be found under the work space for your sketch. The same location as the c_cpp_properties.json file.  If output is not set, Arduino will create a new temporary output folder each time it compiles your sketch, which means it cannot reuse the intermediate result of the previous build, leading to long verify/upload time. So it is recommended to set the field. Arduino requires that the output path should not be the workspace itself or subfolder of the workspace, otherwise, it may not work correctly. By default, this option is not set. Again, no idea why, you will get a warning if it isn't set when you verify.



Whether you use the Arduino IDE or Visual Studio Code, you need to download the Azure IoT library: ESP32_AzureIoT - An Azure IoT Hub library for ESP32 devices in Arduino.  Unzip and copy this to your Arduino libraries folder.

As a first test, load the GetStarted.ino sketch from the examples folder in the library you just downloaded. This sketch will connect to the IoT hub and continuously send messages containing fake data. You will need to fill in the following blanks in the sketch:

  • DEVICE_ID - copy from your IoT registered device;
  • connectionString - copy from the primary connection string;
  • ssid - the displayed name for your WiFi network; and
  • password - for your WiFi network.

Connect to your ESP32, check the port and board type, then compile and upload the sketch. Open up the serial monitor at 115,200 baud so that you can see what is happening. The monitor should be displaying something like the following.


To confirm that the IoT hub is receiving these messages have a look at the Azure dashboard and you should see these messages arriving.


If you have Visual Studio Code, you can use the Azure IoT extension to monitor messages to your IoT hub. Just select your device and then right click and Start monitoring D2C (Device to Cloud) message. You can also send messages from the cloud to your device from here.


Once this was working, I updated the code to just send a heart beat message back to the cloud, letting us know that it was still alive. Every time the ESP32 does this, it broadcasts an IR remote control code three times. This is the usual methodology for remote controls. Currently it is just broadcasting the Sony power code, but we will look at ways we can start/stop the broadcast and change the code via Azure.

Cloud to Device Message Lifecycle




To guarantee at-least-once message delivery, IoT Hub persists cloud-to-device messages in per-device queues. Devices must explicitly acknowledge completion for IoT Hub to remove them from the queue. This approach guarantees resiliency against connectivity and device failures.

When the IoT Hub service sends a message to a device, the service sets the message state to Enqueued. When a device wants to receive a message, IoT Hub locks the message (by setting the state to Invisible), which allows other threads on the device to start receiving other messages. When a device thread completes the processing of a message, it notifies IoT Hub by completing the message. IoT Hub then sets the state to Completed.

The max delivery count property on IoT Hub determines the maximum number of times a message can transition between the Enqueued and Invisible states. After that number of transitions, IoT Hub sets the state of the message to Dead lettered.

The diagram above shows the lifecycle state graph for a cloud-to-device message in IoT Hub. Luckily, sending messages is a lot more straight forward than implementing the message lifecycle.

Controlling the ESP32 via Azure


Now that we have our ESP32 talking to Azure and broadcasting an IR code burst every 10 seconds we want to be able to control this via the cloud. The easiest way to do this is using Visual Studio Code again.


If you right click on the device, shown in Explorer under Azure IOT HUB DEVICES, then the window above is displayed. The two ways we will look at communicating with our device via the cloud is:

  1. Cloud to Device (C2D) Messaging; and
  2. Triggering a defined device method.
You can try out both.

Cloud to Device Messaging


After uploading the sketch to your ESP32, open the serial monitor from the Arduino IDE. I found the serial monitor function in Visual Studio Code was a bit dodgy. Select Send C2D (Cloud to Device) Message to Device, and a message entry window will open. Type in whatever you want and hit return.

In the Azure IoT Toolkit output window you should see:

[C2DMessage] Sending message to [ESP32_IRBeacon_1] ...
[C2DMessage] [Success] Message sent to [ESP32_IRBeacon_1]

A second or so later, the following will appear in the Serial Monitor:

Info: >>>Received Message [1], Size=14 Message test message
Message callback:
test message

The function which handles message handling in the ESP32 code is MessageCallback(const char* payLoad, int size). The call back function is set during setup() using:

Esp32MQTTClient_SetMessageCallback(MessageCallback);


Invoke a Direct Method


This is the way that I chose to control the IR beacon (since that is its purpose). You can define what methods you want to support in your code. Currently we are only handling start and stop but it would be trivial to add another method to set the IR code transmitted.

The process for invoking a method is the same as for sending a C2D message. Right click on your device in Explorer and select "Invoke Direct Method". A text entry window will open, type in your method name (e.g. stop) and hit enter. In the Azure IoT Toolkit output window you should see:

[DirectMethod] Invokeing Direct Method [stop] to [ESP32_IRBeacon_1] ...
[DirectMethod] Invokeing Direct Method [start] to [ESP32_IRBeacon_1] ...

Yes whoever wrote this code couldn't spell invoking! Then in the Serial monitor:

Info: Try to invoke method stop
Info: Stop sending IR burst and heart beat

The function which handles method handling in the ESP32 code is DeviceMethodCallback(). This call back function is set during setup() using:

Esp32MQTTClient_SetDeviceMethodCallback(DeviceMethodCallback);


Conclusion




If all you want is a dashboard for your IoT application then Node-Red is MUCH simpler to implement. If you need an industrial strength solution then you need something like Azure IoT hub.

Actually, using Visual Studio Code (VSC) was a pleasure, and this will be my go to IDE for Arduino from now on. Controlling and Monitoring your IoT devices via VSC was also very easy once you work out how it operates. The user interface is not very discoverable, you need to hit F1 to access most of the Arduino and Azure commands.

I will publish a short follow up article providing a simple PCB to mount the diagnostic LED's, IR transmitting module and the ARM/DEBUG switch. It is a simple enough circuit that you could do it on a breadboard or veroboard. Note that the Duinotech ESP32 doesn't leave any pins free on one side of a standard breadboard, due to its width. See the photo above. This is one reason I decided to use a PCB.


Saturday, March 4, 2017

Raspberry Pi Motor Board Python Class


Overview


We have made some additional changes to the Seeed Raspberry Pi Motor Board class. An obvious missing method is a way to change the speed of the motors. You can of course just change the duty attribute but this will only take effect the next time you change direction. So we have added a speed(duty) method. This will assign the new duty cycle and change the duty cycle of any motors which are already moving.

Note that in the Motor() class provided in the previous post:

def Stop():

should be:

def Stop(self):

Motor Control Class


Here is the updated Motor Control Class for the Seeed Motor Board. You many need to change the names of the direction methods as this will be determined by how you have wired your motors to the motor control board.

The MotorState enum class is used to record a history list of commands received. This may be useful when debugging the Robot in autonomous mode.

#!/usr/bin/python
# RS_MotorControl.py - Motor Control Class for the Seeed Raspberry Pi Motor Driver 
# Board v1.0 which uses the Freescale MC33932 dual H-Bridge Power IC.
#
# Based on Seeed Motor() Class 
# ref: http://wiki.seeed.cc/Raspberry_Pi_Motor_Driver_Board_v1.0/
#
# 1 March 2017 - 1.0 Original Issue
#
# Reefwing Software
# Simplified BSD Licence - see bottom of file.

import RPi.GPIO as GPIO
import os, signal

from time import sleep
from enum import Enum, unique
from PiSoftPwm import *

@unique
class MotorState(Enum):
    INIT         = 1
    STOPPED      = 2
    LEFT_FWD     = 3
    RIGHT_FWD    = 4
    BOTH_FWD     = 5
    LEFT_BACK    = 6
    RIGHT_BACK   = 7
    BOTH_BACK    = 8
    CHANGE_SPEED = 9

class MotorControl():
    def __init__(self, base_time=0.01, duty=50):
        # MC33932 pins connected to GPIO
        self.PWMA = 25  
        self.PWMB = 22
        self._IN1 = 23  
        self._IN2 = 24 
        self._IN3 = 17
        self._IN4 = 27

        self.base_time = base_time
        self.duty = duty
        self.history = [MotorState.INIT]

        # Initialize PWMA & PWMB 
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.PWMA, GPIO.OUT)
        GPIO.setup(self.PWMB, GPIO.OUT)
        GPIO.output(self.PWMA, True)
        GPIO.output(self.PWMB, True)

        # Initialize Software PWM outputs
        # Left Motor  = OUT_1 and OUT_2
        # Right Motor = OUT_3 and OUT_4
        self.OUT_1  = PiSoftPwm(self.base_time, 100, self._IN1, GPIO.BCM)
        self.OUT_2  = PiSoftPwm(self.base_time, 100, self._IN2, GPIO.BCM)
        self.OUT_3  = PiSoftPwm(self.base_time, 100, self._IN3, GPIO.BCM)
        self.OUT_4  = PiSoftPwm(self.base_time, 100, self._IN4, GPIO.BCM)

        # Start PWM for outputs - nbSlicesOn = 0, i.e. duty cycle = 0
        self.OUT_1.start(0)
        self.OUT_2.start(0)
        self.OUT_3.start(0)
        self.OUT_4.start(0)

    def __str__(self):
        # Return string representation of motor control
        return "Motor Control: base time - {0} seconds, duty - {1}%".format(self.base_time, self.duty)

    def left_back(self):
        self.OUT_1.changeBaseTime(self.base_time)
        self.OUT_2.changeBaseTime(self.base_time)
        self.OUT_1.changeNbSlicesOn(self.duty)
        self.OUT_2.changeNbSlicesOn(0)
        self.history.append(MotorState.LEFT_BACK)

    def left_forward(self):
        self.OUT_1.changeBaseTime(self.base_time)
        self.OUT_2.changeBaseTime(self.base_time)
        self.OUT_1.changeNbSlicesOn(0)
        self.OUT_2.changeNbSlicesOn(self.duty)
        self.history.append(MotorState.LEFT_FWD)

    def right_back(self):
        self.OUT_3.changeBaseTime(self.base_time)
        self.OUT_4.changeBaseTime(self.base_time)
        self.OUT_3.changeNbSlicesOn(0)
        self.OUT_4.changeNbSlicesOn(self.duty)
        self.history.append(MotorState.RIGHT_BACK)

    def right_forward(self):
        self.OUT_3.changeBaseTime(self.base_time)
        self.OUT_4.changeBaseTime(self.base_time)
        self.OUT_3.changeNbSlicesOn(self.duty)
        self.OUT_4.changeNbSlicesOn(0)
        self.history.append(MotorState.RIGHT_FWD)

    def speed(self, duty):
        # Change motor speed to duty (0-100) if not stopped (0)
        self.duty = duty
        self.OUT_1.nbSlicesOn = duty if self.OUT_1.nbSlicesOn else 0
        self.OUT_2.nbSlicesOn = duty if self.OUT_2.nbSlicesOn else 0
        self.OUT_3.nbSlicesOn = duty if self.OUT_3.nbSlicesOn else 0
        self.OUT_4.nbSlicesOn = duty if self.OUT_4.nbSlicesOn else 0
        self.history.append(MotorState.CHANGE_SPEED)

    def stop(self):
        self.OUT_1.changeNbSlicesOn(0)
        self.OUT_2.changeNbSlicesOn(0)
        self.OUT_3.changeNbSlicesOn(0)
        self.OUT_4.changeNbSlicesOn(0)
        self.history.append(MotorState.STOPPED)
        
    def cleanup(self):
        # Stop PWM on all outputs
        self.OUT_1.stop()
        self.OUT_2.stop()
        self.OUT_3.stop()
        self.OUT_4.stop()

def main():
    motor_control = MotorControl()    # create a new motor control instance
    print(motor_control)

    def endProcess(signum = None, frame = None):
        # Called on process termination. Stop motor control PWM
        if signum is not None:
            SIGNALS_NAMES_DICT = dict((getattr(signal, n), n) for n in dir(signal) if n.startswith('SIG') and '_' not in n )
            print("signal {} received by process with PID {}".format(SIGNALS_NAMES_DICT[signum], os.getpid()))
        print("\n-- Terminating program --")
        print("Cleaning up motor control PWM and GPIO...")
        motor_control.cleanup()
        GPIO.cleanup()
        print("Done.")
        exit(0)

    # Assign handler for process exit
    signal.signal(signal.SIGTERM, endProcess)
    signal.signal(signal.SIGINT, endProcess)
    signal.signal(signal.SIGHUP, endProcess)
    signal.signal(signal.SIGQUIT, endProcess)

    while True:
        print('Testing motors...')
        motor_control.left_forward()
        sleep(1)
        motor_control.left_back()
        sleep(1)
        motor_control.right_forward()
        sleep(1)
        motor_control.right_back()
        sleep(1)
        # speed = int(input("Enter Speed (0-100, CTRL c to quit): "))
        # motor_control.speed(speed)
        
if __name__ == "__main__":
    # execute only if run as a script
    main()

## Copyright (c) 2017, Reefwing Software
## All rights reserved.
##
## Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are met:
##
## 1. Redistributions of source code must retain the above copyright notice, this
##   list of conditions and the following disclaimer.
## 2. Redistributions in binary form must reproduce the above copyright notice,
##   this list of conditions and the following disclaimer in the documentation
##   and/or other materials provided with the distribution.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
## ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
## WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
## DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
## ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
## (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
## LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
## SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



Wednesday, January 6, 2016

Bluetooth (BLE) Robot Remote Control using an iPhone - Part 1

Overview


Any self respecting autonomous robot needs a remote control / telemetry mode. This is useful for testing and most importantly fun! There are lots of different ways that you could approach this (e.g. hard wired, WiFi, or RF) but we liked the idea of using our iPhone as the controller and this seemed like a good excuse to play around with Bluetooth.

Figure 1. The Bluno - Arduino Uno + BLE.

As this is a fairly meaty subject, we will break it up into three posts. Each post will cover the following:

  1. Bluno - An Arduino board combined with a Bluetooth 4 Low Energy module produced by DFRobot (see Figure 1). In our design this acts as the middle-ware, providing a bridge between our iPhone app and the Arduino Mega 2560 which acts as our main robot controller. You could control a simple robot directly from the Bluno. We also used the Bluno accessory shield, mostly to provide an indication of what is happening via its OLED display. However, this doesn't leave a lot of spare pins (3 digital and 2 analog to be precise).
  2. iOS App - I will provide the source code for this and make it available for download from iTunes once I have finished debugging. It should be flexible enough to use in your own robot design. I have included the ability to remap the keys should you wish to use different characters to the ones I selected.
  3. Mega 2560 Integration - This is the final step in allowing your robot to be controlled via an iPhone app. The Bluno communicates with the Mega 2560 using the serial 3 comms port. We could have used the I2C bus, but as this is used for logging it could be tied up when a critical stop command was trying to be sent from the remote. As a remote command is one of the highest priority behaviours, it made sense for it to have its own dedicated communication channel.

The Design


The wiring is simple. As shown in Figure 2, the Bluno connects to our Mega 2560 using 2 wires. Pins 4 and 5 of the Bluno connects to pins 14 and 15 on the Mega 2560 (the Serial 3 Tx and Rx pins). That's it, the magic happens in the software. We have described in an earlier post how the Mega 2560 controls our HB-25 Motor Controllers. Bluno uses a TI CC2540 BT 4.0 chip to provide BLE functionality. We will use this to communicate with the iPhone.

Figure 2. AVA Schematic.


The Hardware


We are using the SoftwareSerial library on the Bluno so you can use any 2 spare digital pins, but if you also use the accessory shield (Figure 3) then you wont have many other options. The accessory shield provides quite a bit of capability.


128x64 OLED Screen                                    Display messages from your phone interface.
Buzzer                                                            Enable Sound notifications or simple music.
DHT11 Temperature & Humidity Sensor     For environmental monitoring.
1.5A Relay                                                     Device switch or integrating with other electronics.
Helical Potentiometer                                    Transfer real time data to your phone
RGB LED                                                      Display full colour RGB
Mini Joystick                                                 Tells your phone which direction is pressed


The pins used by BLE module and the accessory shield are:

0    BLE Rx (also used for programming and for the terminal comms).
1    BLE Tx (also used for programming and for the terminal comms).
2    DHT11 Temp / Humidity sensor.
3    RGB LED (blue control).
4    Spare - used for serial Rx, connects to Mega 2560 Tx.
5    Spare - used for serial Tx, connects to Mega 2560 Rx.
6    OLED RESET
7    OLED DC
8    Buzzer
9    RGB LED (red control)
10  RGB LED (green control)
11  Relay
12  Spare
13  LED

A0    Joystick
A1    Knob (potentiometer)
A2    Spare
A3    Spare
A4    I2C - SDA
A5    I2C - SCL

Figure 3. The DFRobot Accessory Shield.

Looking at Figure 3 you will note our first design problem. None of the pins are accessible without breaking out the soldering iron. To allow us to connect our 2 serial wires we purchased a prototyping screw terminal shield from DFRobot (Figure 4).

Figure 4. Prototyping Screw Terminal Shield.

The proto shield is sandwiched between the Bluno and the accessory shield (Figure 5). Excellent we now have access to pins 4 and 5, BUT now the OLED display no longer works. Design problem number 2. For some unfathomable reason (particularly since all these products come from the same supplier) the proto shield is 2 pins short. The missing pins are the I2C SDA and SCL (used to control the OLED). There are a number of ways you can fix this, but the easiest is to get an Arduino Header Kit (like the one shown in Figure 5) and use the 6 pin header to bridge between the Bluno and the accessory shield. There are holes in the proto shield to allow this (albeit not exactly in the right spot). Cut off the 4 unused pins to prevent them shorting on anything. You can solder the header kit onto the proto board if you want but we didn't find this necessary.

Figure 5. Arduino Header Kit.

The images below (Figure 6) show the boards connected together and mounted on AVA. The yellow and brown wires are the serial comms to the Mega 2560 and the red and green are power and go to the distribution board on the bottom deck. The board adjacent to the Bluno is used for monitoring of the 2 x 12V SLA batteries. The panel voltmeter displays the current battery voltage (11.5 VDC).



Figure 6. Bluno, Proto and Accessory Shield.

The Software


You can download the Bluno software from the Reefwing Gist Repository. The code is pretty straight forward. The Bluno waits until it receives data on its serial port from the BLE module and passes it to the Mega via another serial port. The RGB LED flashes blue once a second while waiting for data. It will flash green once when data is received. The iOS app allows you to select a number of tasks for the robot to perform. In Figure 6, you can see that the current task is task 2: Patrol. When task 1, Remote Control is selected, the RGB LED goes a solid red.

The main code loop is shown below. As we will see in the next post, the iPhone app sends a character code to indicate the command required (e.g. 's' means stop).

if (Serial.available())  {
        char data = Serial.read();
        commsDetected = true;
        switch (data) {
            case 's':
                Serial.write("Ack - STOP");
                megaSerial.write(data);
                break;
            case 'f':
                Serial.write("Ack - Forward");
                megaSerial.write(data);
                break;
            case 'b':
                Serial.write("Ack - Back");
                megaSerial.write(data);
                break;
            case 'l':
                Serial.write("Ack - Left");
                megaSerial.write(data);
                break;
            case 'r':
                Serial.write("Ack - Right");
                megaSerial.write(data);
                break;
            case 'w':
                Serial.write("Ack - F1");
                megaSerial.write(data);
                break;
            case 'x':
                Serial.write("Ack - F2");
                megaSerial.write(data);
                break;
            case 'y':
                Serial.write("Ack - F3");
                megaSerial.write(data);
                break;
            case 'z':
                Serial.write("Ack - F4");
                megaSerial.write(data);
                break;
            case '0':
                task = statusReport;
                taskDescription = "T0: Status";
                Serial.print(taskDescription);
                megaSerial.write(data);
                remoteControlled = false;
                break;
            case '1':
                task = remoteControl;
                taskDescription = "T1: Remote";
                Serial.print(taskDescription);
                megaSerial.write(data);
                remoteControlled = true;
                break;
            case '2':
                task = patrol;
                taskDescription = "T2: Patrol";
                Serial.print(taskDescription);
                megaSerial.write(data);
                remoteControlled = false;
                break;
            case '3':
                task = followIR;
                taskDescription = "T3: Follow IR";
                Serial.print(taskDescription);
                megaSerial.write(data);
                remoteControlled = false;
                break;
            case '4':
                task = avoidIR;
                taskDescription = "T4: Avoid IR";
                Serial.print(taskDescription);
                megaSerial.write(data);
                remoteControlled = false;
                break;
            default:
                char errorMsg[32];
                String error = "Unknown Command - ";
                error += data;
                error.toCharArray(errorMsg, 32);
                Serial.write(errorMsg);
                break;
        }
        Serial.println();
    }