Red Amber Windows Tutorial

Contents

NOTICE
Since version 1.1 the callback OnCombinedData contains an additional parameter (timestamp), if you are updating from an older version of the SDK please make sure to add this parameter.

Overview

The new GemSDK for Windows is not backward-compatible with older Blue Amber models.

GemSDK features:

GemSDK provides you with a native DLL (GemSDK.dll) which has a C-interface and therefore can be used easily with any language supporting C bindings.

Installation

Requirements

Setting up the driver

Because of current limitation of the Windows Win32 BLE API, GemSDK needs exclusive control of the BLE controller, so you need to replace the driver of your BLE controller with a WinUSB driver, which gives the GemSDK raw USB-level access to the controller.

Note: While the driver is replaced, you will not be able to use the controller for any other BLE devices. If you have any computer peripherals (mouse, keyboard, speakers, etc…) that rely on BLE, we recommend using an external dongle (or another one if that’s what your using for BLE) when working with the Gems.
To restore the original driver, go to Control Panel > Device Manager > Universal Serial Bus devices, right click on your device and choose “Update Driver Software…”. On the window that opened click on “Search automatically for updated driver software”.

Hopefully in the future we will be able to use Win32 BLE API as-is but currently it is not possible. And if not, at least simplify the installation procedure of the alternate driver.

The following steps will show you how to replace the driver with a program called “Zadig”.

  1. Download the Zadig program from their official website (no installation required, the program is a single .exe)
  2. Open the program and you should see the following window:
    zadig_step1
  3. Then click Options > List All Devices
    zadig_step2
  4. Choose your device from the list (in our case, it’s a dongle with the BCM20702A0 chipset)
    zadig_step3
  5. Make sure the selected driver is WinUSB (your version might be different), and click the “Replace Driver” button.
    zadig_step4
  6. Click yes on the popup message
    zadig_step5
  7. If all went well, you should see “Driver Installation: Success” on the bottom left corner of the window:
    zadig_step6

Getting Started

After downloading and installing all the necessary things according to the installation section, you are now ready to code!

Before using any function in the library, you’ll need to initialize it with a call to Gem_Initialize:

GemStatusCode err = Gem_Initialize();

if(err != GEM_SUCCESS)
    return 1;

This will initialize all the necessary GemSDK needs, mainly initialing the BLE controller, which means that if this function returns GEM_SUCCESS then you set-up everything successfully!

Now to connect to your Gem, you’ll have to do the following:

GemHandle _h;

Gem_Get("00:80:98:3C:3B:E5", &_h);
Gem_SetOnStateChanged(_h, OnStateChanged);
Gem_Connect(_h);

First, you need to call Gem_Get, which will give you back an initialized handle for the Gem with the given address. Notice how we set-up the OnStateChanged callback before calling Gem_Connect, this is important because a lot of functions won’t be available until the Gem is connected. OnStateChanged will be called each time the Gem’s connection state changes. The Gem has 4 connection states: Connected, Connecting, Disconnect and Disconnecting. In Connected state you are allowed to start calling all the extension and motion related functions.

This is the full source code so far:

#include <iostream>
#include <GemSDK.h>

GemHandle _handle;

void OnStateChanged(GemState state)
{
    std::cout << "gem state: ";

    switch (state)
    {
    case GemState::Connected:
        std::cout << "Connected";
        break;
    case GemState::Connecting:
        std::cout << "Connecting";
        break;
    case GemState::Disconnected:
        std::cout << "Disconnected";
        break;
    case GemState::Disconnecting:
        std::cout << "Disconnecting";
        break;
    }

    std::cout << "\n";
}

int main(int argc, char const* argv[])
{
    GemStatusCode err = Gem_Initialize();

    if(err != GEM_SUCCESS)
        return 1;

    Gem_Get("00:80:98:3C:3B:E5", &_handle);
    Gem_SetOnStateChanged(_handle, OnStateChanged);
    Gem_Connect(_handle);
    getchar(); // prevent the program from terminating        

    return 0;
}

Most of the features are easily used by just registering a callback and enabling the feature. In the following sections you will see how to that for different features.

Getting Motion Data

Getting motion data requires you to register a callback and calling Gem_EnableCombinedData():

void OnCombinedData(unsigned short timestamp, const float* quaternion, const float* acceleration)
{
    std::cout << "timestamp: " << timestamp << "\n";
    std::cout << "quaternion: " << quaternion[0] << ", " << quaternion[1] << ", " << quaternion[2] << ", " << quaternion[3] << "\n";
    std::cout << "acceleration: " << acceleration[0] << ", " << acceleration[1] << ", " << acceleration[2] << "\n";
}

Gem_SetOnCombinedData(_handle, OnCombinedData);
Gem_EnableCombinedData(_handle);

Please note that calling Gem_EnableCombinedData() is only allowed on Gems in a Connected state!

Handling Taps

Getting tap data is very similar to getting motion data:

void OnTapData(unsigned int direction)
{
    std::cout << "OnTapData, direction: " << direction << "\n";
}

Gem_SetOnTapData(_handle, OnTapData);
Gem_EnableTapData(_handle);

Please note that calling Gem_EnableTapData() is only allowed on Gems in a Connected state!

Pedometer

Getting pedometer data is very similar to getting motion data:

void OnPedometerData(unsigned int steps, float walk_time)
{
    std::cout << "OnPedometerData, steps: " << steps << ", walk_time: " << walk_time << "\n";
}

Gem_SetOnPedometerData(_handle, OnPedometerData);
Gem_EnablePedometerData(_handle);

Please note that calling Gem_EnablePedometerData() is only allowed on Gems in a Connected state!

Extensions

Red Amber supports 8 GPIOs, 3 Analog inputs, 1 PWM output, I2C and SPI bus communications.
All this functionality is available via SDK. Physically additional sensors, LED’s, Buttons, and so on should be connected to corresponding pins of development board.

To make effect all the following commands should be executed when Gem is connected!

GPIOs

All the GPIOs will return to the initial state after disconnection.

Any GPIO pin should be configured before use

Gem_ConfigureDigital(_handle, 0, PinIoConfiguration::Output);
Gem_ConfigureDigital(_handle, 1, PinIoConfiguration::Input);

// InputNotify will generate an event when a change is detected on the corresponding pin
Gem_ConfigureDigital(_handle, 5, PinIoConfiguration::InputNotify);

In addition inputs can be configured with internal pull-up or pull-down resistor:

Gem_ConfigureDigital(_handle, 2, PinIoConfiguration::InputPulldown);
Gem_ConfigureDigital(_handle, 3, PinIoConfiguration::InputNotifyPullup);

Configuration can also be used to reset any GPIO to its initial state (high-impedance input):

Gem_ConfigureDigital(_handle, 1, PinIoConfiguration::Reset);

Then you can set them or read their values.
Result of read requests will be returned via the callback set with Gem_SetOnIoDigitalRead() (see the source code below for an example)

Gem_WriteDigital(_handle, 0, IoValues::High);
Gem_ReadDigital(_handle, 1);

Analog inputs

Analog inputs reads the voltage on corresponding pin

Gem_ReadAnalog(0);

Result of read requests (0..3.6V range) will be returned via the callback set with Gem_SetOnIoAnalogRead() (see the source code below for an example)
Please note that the pin id used in the Gem_ReadAnalog() function is not the same as the one we used before in Gem_ConfigureDigital/Gem_WriteDigital/Gem_ReadDigital, analog pins are not the same as the digital ones! (i.e analog pin 0 is not the same physical pin as digital pin 0). For more info refer to the the Gem pinout graph.

PWM (Pulse-Width Modulation)

Gem_WritePWM(_handle, 2.5f);

Sets PWM output to certain voltage 0V..3.6V. The value is limited by the board supply voltage. For example, with the battery extension board the supply voltage is 3V.

I2C

I2C read/write requests can contain up to 16 bytes each.
Result of read requests will be returned via the callback set with Gem_SetOnI2CData() (see the source code below for an example)

unsigned char buffer[] = { 0x00, 0x12, 0x14 };    

// Write data to an I2C device with the address 0x5C
Gem_WriteI2CData(_handle, 0x5C, buffer, sizeof(buffer));

// Write data to a register 0x02 of an I2C device with the address 0x5C
Gem_WriteI2CDataReg(_handle, 0x5C, 0x02, buffer, sizeof(buffer));

// Read 4 bytes from an I2C device with the address 0x5C
Gem_ReadI2CData(_handle, 0x5C, 4);

//Read 4 bytes from register 0x02 of an I2C device with the address 0x5C
Gem_ReadI2CDataReg(_handle, 0x5c, 0x02, 4);

SPI

SPI read/write requests can contain up to 16 bytes each.
Result of read requests with the corresponding request id will be returned via the callback set with Gem_SetOnSPIData() (see the source code below for an example)

unsigned char buffer[] = { 0x00, 0x12, 0x14 };

// Read 5 bytes from SPI bus, we choose request id to be 100 (arbitrarily) so we will be able to match this request to the response (via callback) later
Gem_ReadSPI(_handle, 100, 5);

// Write data to SPI 
Gem_WriteSPI(_handle, buffer, sizeof(buffer));

// Writes and reads data simultaneously to/from the SPI bus (the amount of data read is the same as the data written)
// we choose request id to be 101 (arbitrarily) so we will be able to match this request to the response (via callback) later
Gem_ReadWriteSPI(_handle, 101, buffer, sizeof(buffer));

Callbacks

The callbacks will look like this:

void OnIoDigitalRead(int id, IoValues value)
{
    std::cout << "OnIoDigitalRead, id: " << id << ", value: " << (int)value << std::endl;
}

void OnIoAnalogRead(int id, int value)
{
    std::cout << "OnIoAnalogRead, id: " << id << ", value: " << (int)value << std::endl;
}

void OnI2CData(unsigned char address, unsigned char reg, const unsigned char* data, unsigned char data_length)
{
    std::cout << "OnI2CData, address: " << (int)address << ", reg: " << reg << ", data_length: " << std::dec << (int)data_length << "\n";
    std::cout << std::hex;

    for (int i = 0; i < data_length; i++)
        std::cout << (int)data[i] << " ";

    std::cout << std::dec << "\n";
}

void OnSPIData(unsigned char requestId, const unsigned char* data, unsigned char data_length)
{
    std::cout << "OnSPIData, requestId: " << (int)requestId << ", data_length: " << std::dec << (int)data_length << "\n";
    std::cout << std::hex;

    for (int i = 0; i < data_length; i++)
        std::cout << (int)data[i] << " ";

    std::cout << std::dec << "\n";
}

Full source code example

#include <iostream>
#include <GemSDK.h>

GemHandle _handle;

void OnCombinedData(const float* quaternion, const float* acceleration)
{
    std::cout << "quaternion: " << quaternion[0] << ", " << quaternion[1] << ", " << quaternion[2] << ", " << quaternion[3] << "\n";
    std::cout << "acceleration: " << acceleration[0] << ", " << acceleration[1] << ", " << acceleration[2] << "\n";
}

void OnTapData(unsigned int direction)
{
    std::cout << "OnTapData, direction: " << direction << "\n";
}

void OnPedometerData(unsigned int steps, float walk_time)
{
    std::cout << "OnPedometerData, steps: " << steps << ", walk_time: " << walk_time << "\n";
}

void OnIoDigitalRead(int id, IoValues value)
{
    std::cout << "OnIoDigitalRead, id: " << id << ", value: " << (int)value << std::endl;
}

void OnIoAnalogRead(int id, int value)
{
    std::cout << "OnIoAnalogRead, id: " << id << ", value: " << (int)value << std::endl;
}

void OnI2CData(unsigned char address, unsigned char reg, const unsigned char* data, unsigned char data_length)
{
    std::cout << "OnI2CData, address: " << (int)address << ", reg: " << reg << ", data_length: " << std::dec << (int)data_length << "\n";
    std::cout << std::hex;

    for (int i = 0; i < data_length; i++)
        std::cout << (int)data[i] << " ";

    std::cout << std::dec << "\n";
}

void OnSPIData(unsigned char requestId, const unsigned char* data, unsigned char data_length)
{
    std::cout << "OnSPIData, requestId: " << (int)requestId << ", data_length: " << std::dec << (int)data_length << "\n";
    std::cout << std::hex;

    for (int i = 0; i < data_length; i++)
        std::cout << (int)data[i] << " ";

    std::cout << std::dec << "\n";
}

void OnStateChanged(GemState state)
{
    std::cout << "gem state: ";
    unsigned char buffer[] = { 0x00, 0x12, 0x14 };

    switch (state)
    {
    case GemState::Connected:
        std::cout << "Connected";

        // Remember that all those functions can only be called when the Gem is a Connected state, 
        // so for this simple demo we will do all of the work right when the Gem's state is changed to Connected.

        Gem_EnableCombinedData(_handle);
        Gem_EnableTapData(_handle);
        Gem_EnablePedometerData(_handle);

        Gem_ConfigureDigital(_handle, 0, PinIoConfiguration::Output);
        Gem_ConfigureDigital(_handle, 1, PinIoConfiguration::Input);

        // InputNotify will generate an event when a change is detected on the corresponding pin
        Gem_ConfigureDigital(_handle, 5, PinIoConfiguration::InputNotify);

        // In addition inputs can be configured with internal pull-up or pull-down resistor:
        Gem_ConfigureDigital(_handle, 2, PinIoConfiguration::InputPulldown);
        Gem_ConfigureDigital(_handle, 3, PinIoConfiguration::InputNotifyPullup);

        // Configuration can also be used to reset any GPIO to its initial state (high-impedance input):
        Gem_ConfigureDigital(_handle, 2, PinIoConfiguration::Reset);

        Gem_WriteDigital(_handle, 0, IoValues::High);

        // Result of read requests will be returned via the callback set with Gem_SetOnIoDigitalRead()
        Gem_ReadDigital(_handle, 1);

        // Sets PWM output to certain voltage 0V..3.6V
        Gem_WritePWM(_handle, 2.5f);

        // Write data to an I2C device with the address 0x5C
        Gem_WriteI2CData(_handle, 0x5C, buffer, sizeof(buffer));

        // Write data to a register 0x02 of an I2C device with the address 0x5C
        Gem_WriteI2CDataReg(_handle, 0x5C, 0x02, buffer, sizeof(buffer));

        // Read 4 bytes from an I2C device with the address 0x5C
        Gem_ReadI2CData(_handle, 0x5C, 4);

        //Read 4 bytes from register 0x02 of an I2C device with the address 0x5C
        Gem_ReadI2CDataReg(_handle, 0x5c, 0x02, 4);

        // Read 5 bytes from SPI bus, we choose request id to be 100 (arbitrarily) so we will be able to match this request to the response (via callback) later
        Gem_ReadSPI(_handle, 100, 5);

        // Write data to SPI 
        Gem_WriteSPI(_handle, buffer, sizeof(buffer));

        // Writes and reads data simultaneously to/from the SPI bus (the amount of data read is the same as the data written)
        // we choose request id to be 101 (arbitrarily) so we will be able to match this request to the response (via callback) later
        Gem_ReadWriteSPI(_handle, 101, buffer, sizeof(buffer));

        break;
    case GemState::Connecting:
        std::cout << "Connecting";
        break;
    case GemState::Disconnected:
        std::cout << "Disconnected";
        break;
    case GemState::Disconnecting:
        std::cout << "Disconnecting";
        break;
    }

    std::cout << "\n";
}

int main(int argc, char const* argv[])
{
    // Before calling any other functions in the GemSDK we need to initialize it:
    GemStatusCode err = Gem_Initialize();

    if(err != GEM_SUCCESS)
        return 1;

    // Get a Gem handle for our Gem
    Gem_Get("00:80:98:3C:3B:E5", &_handle);

    // This callback is important to set before connecting, so we will not miss any connection state changed events
    Gem_SetOnStateChanged(_handle, OnStateChanged);

    // We set all the data callbacks we intereseted in before connecting to the Gem
    Gem_SetOnCombinedData(_handle, OnCombinedData);
    Gem_SetOnTapData(_handle, OnTapData);
    Gem_SetOnPedometerData(_handle, OnPedometerData);
    Gem_SetOnIoDigitalRead(_handle, OnIoDigitalRead);
    Gem_SetOnIoAnalogRead(_handle, OnIoAnalogRead);
    Gem_SetOnI2CData(_handle, OnI2CData);
    Gem_SetOnSPIData(_handle, OnSPIData);

    // Now we are ready to connect to the Gem
    Gem_Connect(_handle);

    getchar(); // prevent the program from terminating

    // Calling this when you are done using the Gem is very important! It will cleanup all the resources used by
    // GemSDK and will release the BLE controller to work with other programs.
    Gem_Terminate();

    return 0;
}