Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Thursday, 7 December 2017

CSR1010 Bluetooth Low Energy (BLE) Demo With Source

There are many Bluetooth Low Energy chips on the market, by a chance I got a CSR development system and did some experiments with it.

CSR1010 is a BLE SoC and the development system is based on C. You don't need to deal with the low level Bluetooth protocols, all you need to do is creating your own service and using the APIs.

To be honest, the CSR development system has only very limited debugging abilities, you can somewhat debug your code doing such as 'stepping' and set break point, but there's no way you can see the values of your variables, so it makes little sense to do the debugging.

Fortunately, my code runs and it seems OK.

Let's describe the functionalities of this simple demo first, besides the basic mandatory BLE services, there is only one user service created, it has 2 characteristics, one is feeding a random data of 1 byte to the client, when the notification is turned on, the Bluetooth board will push the data to the mobile phone every 2 seconds. And the another one is 24-bit and has both read and write properties, this data is used to control the tri-color LED on board. The MSB byte is for the red LED, the LSB is used to control the blue. The number controls the duty cycle of the PWM, 0 is OFF, and set to 0xff you get 100% duty cycle the LED will be in full brightness.

The user service is created like this:

#ifndef JJ_TEST_SERVICE_H
#define JJ_TEST_SERVICE_H

primary_service
{
    uuid : 333f5834-123e-4f2b-9da8-8e863c322e5f,
    name : "JJ_test_service",
    
    characteristic
    {
        uuid : e8069f93-c150-48ef-a02d-589b652e9538,
        name : "RANDOM_NUMBER",
        flags : [FLAG_IRQ, FLAG_ENCR_R],
        properties : [read, notify],
        value : 0x00,
        client_config
        {
            flags : [FLAG_IRQ],
            name : "RANDOM_NUMBER_C_CFG"
        }
    },
    
    characteristic
    {
        uuid : d1dea016-a7fb-4dfa-84c5-b24158669e20,
        name : "LIGHT_CTL",
        flags : [FLAG_IRQ, FLAG_ENCR_R],
        properties : [read, write],
        size_value : 3
    }
}


#endif

This demo is based on the CSR demo application "gatt_server", as the whole source code is too much, I only include the user created part here.

JJ_test_service.c:

/*============================================================================*
 *  SDK Header Files
 *===========================================================================*/

#include <gatt.h>           /* GATT application interface */
#include <buf_utils.h>      /* Buffer functions */
#include <random.h>
#include <pio.h>


/*============================================================================*
 *  Local Header Files
 *===========================================================================*/

#include "gatt_server.h"    /* Definitions used throughout the GATT server */
#include "app_gatt_db.h"    /* GATT database definitions */
#include "JJ_test_service.h"

#define PIO_BUTTON      1          /* PIO connected to the button on CSR10xx */
#define PIO_LED_RED     9          /* PIO connected to the RED on CSR10xx */
#define PIO_LED_GREEN   10           /* PIO connected to the GREEN on CSR10xx */
#define PIO_LED_BLUE    11          /* PIO connected to the BLUE on CSR10xx */

#define PIO_DIR_OUTPUT  TRUE        /* PIO direction configured as output */
#define PIO_DIR_INPUT   FALSE       /* PIO direction configured as input */

gatt_client_config jj_client_cfg;
typedef struct _LIGHT_CTL
{
    uint8   red;
    uint8   green;
    uint8   blue;
} LIGHT_CTL;

LIGHT_CTL jj_light_ctrl;

extern void JJ_Test_InitChipReset(void)
{
    PioSetMode(PIO_LED_RED, pio_mode_pwm3);
    PioSetMode(PIO_LED_GREEN, pio_mode_pwm1);
    PioSetMode(PIO_LED_BLUE, pio_mode_pwm2);
    PioSetDir(PIO_LED_RED, PIO_DIR_OUTPUT);
    PioSetDir(PIO_LED_GREEN, PIO_DIR_OUTPUT);
    PioSetDir(PIO_LED_BLUE, PIO_DIR_OUTPUT);
    PioEnablePWM(1, TRUE);
    PioEnablePWM(2, TRUE);
    PioEnablePWM(3, TRUE);
    PioConfigPWM(3, pio_pwm_mode_inverted_push_pull,
                 0, 255, 0,
                 0, 255, 255, 0);
    PioConfigPWM(1, pio_pwm_mode_inverted_push_pull,
                 0, 255, 0,
                 0, 255, 255, 0);
    PioConfigPWM(2, pio_pwm_mode_inverted_push_pull,
                 0, 255, 255,
                 0, 255, 255, 0);
}

extern void JJ_Test_DataInit(void)
{
    jj_client_cfg = gatt_client_config_none;
}

extern void JJ_Test_ReadDataFromNVM(uint16 *nvm_offset)
{
}

extern bool JJ_Test_CheckHandleRange(uint16 handle)
{
    return ((handle >= HANDLE_JJ_test_service) &&
            (handle <= HANDLE_JJ_test_service_END))
            ? TRUE : FALSE;
}

extern void JJ_Test_HandleAccessRead(GATT_ACCESS_IND_T *p_ind)
{
    sys_status rc = sys_status_success; /* Function status */
    uint16 result;
    switch (p_ind->handle)
    {
        case    HANDLE_RANDOM_NUMBER:
            result = Random16();
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 1, (uint8*)&result);
            break;
        case    HANDLE_RANDOM_NUMBER_C_CFG:
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 2, (uint8*)&jj_client_cfg);
            break;
        case    HANDLE_LIGHT_CTL:
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 3, (uint8*)&jj_light_ctrl);
            break;
        default:
            rc = gatt_status_read_not_permitted;
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 0, NULL);
    }
}

extern void JJ_Test_HandleAccessWrite(GATT_ACCESS_IND_T *p_ind)
{
    sys_status rc = sys_status_success; /* Function status */
    switch (p_ind->handle)
    {
        case    HANDLE_RANDOM_NUMBER_C_CFG:
            jj_client_cfg = BufReadUint16(&p_ind->value);
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 0, NULL);
            break;
        case    HANDLE_LIGHT_CTL:
            jj_light_ctrl = *((LIGHT_CTL*)p_ind->value);
            PioConfigPWM(3, pio_pwm_mode_inverted_push_pull,
                         jj_light_ctrl.red, 255-jj_light_ctrl.red, 0,
                         jj_light_ctrl.red, 255-jj_light_ctrl.red, 255, 0);
            PioConfigPWM(1, pio_pwm_mode_inverted_push_pull,
                         jj_light_ctrl.green, 255-jj_light_ctrl.green, 0,
                         jj_light_ctrl.green, 255-jj_light_ctrl.green, 255, 0);
            PioConfigPWM(2, pio_pwm_mode_inverted_push_pull,
                         jj_light_ctrl.blue, 255-jj_light_ctrl.blue, 0,
                         jj_light_ctrl.blue, 255-jj_light_ctrl.blue, 255, 0);
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 0, NULL);
        default:
            rc = gatt_status_write_not_permitted;
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 0, NULL);
    }
}

void notificationTimerHandler(timer_id tid)
{
    uint16   temp;
    temp = Random16();
    if (jj_client_cfg == gatt_client_config_notification)
    {
        GattCharValueNotification (GetConnectionID(), HANDLE_RANDOM_NUMBER, 1, (uint8*)&temp);
    }
    TimerCreate(2*SECOND, TRUE, notificationTimerHandler);
}

As CSR is not providing a mobile phone app to be used, I'll use a generic BLE app from Nordic, another Bluetooth chip manufacture. That seems a bit weird but it works.

A video:

Saturday, 29 July 2017

Wirtting code in C++ for small MCUs

When you think about C++ what's in your mind? I thought about those fancy complicated powerful GUI programs on PC like you until one day when I was looking at the IAR MSP430 IDE and realized it has been suported C++ for a long time.

If you look around you'll find that many compilers for MCU has the ability to support C++, for example the IAR, Keil, and the GCC. Wild ranges of MCUs can be developed in C++, like TI's MSP430, Atmel's AVR, ARM series from any manufacture, and even the MCS-51 if you choose the right compiler.

First of all, C++ doesn't mean big code size. Programs with GUI on PC becomes large because they use a lot of libraries, C++ itself is as efficient as C, if not better than. I had done a commercial project with MSP430F2312, which has only 8KB of FLASH. I wrote the entire code in C++ and it turns out to be one of the most efficient code I have ever done. Until now I still couldn't believe I had packed so many functionalities in such a small chip.

C++ has 3 major benefits over C, inheritance, polymorphism, and templates. Unfortunately, many C++ compilers for MCUs do not support templates, but polymorphism alone is a thing good enough for you to consider using it. Imagining you wrote a software I2C interface, and you can easily change the configuration to assign which I/O port to assign it, or even changing the mapping I/O on the fly!

Here I'll give an easy example with STM32F103 using Keil's SDK. The code is very simple, it just blink a LED on an I/O pin. Not using any RTOS, not using any timer, interrupt, or any library.

First there is the plain C version:
#include <stm32f10x.h>

#define LED1        0x2000      // PC13

void RCC_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
}

void blink()
{
    unsigned long i;
    while(1)
    {
        GPIOC->ODR ^= LED1;
        for (i=0; i<0x400000; i++);
    }
}    

int main(void)
{
    RCC_Init();
    GPIOC->CRH = 0x44244444;
    blink();
    return 0;
}

The C++ version is a little bit longer than the C code, because there need to be the definitions of the class:
file gpio.h:
#ifndef GPIO_H
#define GPIO_H

class GPIO
{
    public:
    enum Mode
    {
        FLOAT_INPUT = 0x04,
        PUSH_PULL_50M = 0x03,
        PUSH_PULL_2M = 0x02,
        PUSH_PULL_10M = 0x01,
        ANALOG_INPUT = 0x00,
        ALT_PUSH_PULL_50M = 0x0B,
        ALT_PUSH_PULL_2M = 0x0A,
        ALT_PUSH_PULL_10M = 0x09,
        PULL_UP_DOWN_INPUT = 0x08
    };
    GPIO() {}
    virtual ~GPIO() {}
    virtual void Set1() = 0;
    virtual void Set0() = 0;
    virtual void Toggle() = 0;
};

#endif

File main.cpp:
#include "stm32f10x.h"
#include "gpio.h"

class PortCPin : public GPIO
{
    private:
    uint16_t pin;
    public:
    PortCPin( unsigned char pin_number, GPIO::Mode mode = FLOAT_INPUT)
    {
        pin = (uint16_t)1<<pin_number;
        if (pin_number >= 8)
        {
            pin_number -= 8;
            GPIOC->CRH = (GPIOC->CRH & (~((uint32_t)0x0F << (pin_number*4)))) | ((uint32_t)mode << (pin_number*4));
        }
        else
        {
            GPIOC->CRL = (GPIOC->CRL & (~((uint32_t)0x0F << (pin_number*4)))) | ((uint32_t)mode << (pin_number*4));
        }
    }
    ~PortCPin()
    {
    }
    void Set1()
    {
        GPIOC->BSRR = pin;
    }
    void Set0()
    {
        GPIOC->BRR = pin;
    }
    void Toggle()
    {
        GPIOC->ODR ^= pin;
    }
};

void RCC_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
}

void blink(GPIO& pin)
{
    while(1)
    {
        pin.Toggle();
        for (unsigned long i=0; i<0x400000; ++i);
    }
}

int main()
{
    RCC_Init();
    PortCPin    PC13(13, GPIO::PUSH_PULL_2M);
    blink(PC13);
    return 0;
}

The idea behind the code is, the GPIO is a base class of all the independent I/O pins, it provides a common interface for all the I/O operations (here I only included Set1(), Set0(), Toggle() 3 operations, there should be more like read() change_mode() etc). A specific I/O pin should be an instance of a derived class of GPIO. All the functions in the code should use these common interface to operate I/Os, then by power of polymorphism, once these functions are written, they can be used on any I/O pins.

One thing should be of caution is, the definition of PC13 must be behind the RCC_Init() code, it can not be declared as a global, because then the constructor will be executed before the main() then before the RCC is initialized.

If you are curious about the code efficiency, the plain C code is 744 bytes long (mainly because of the system initialization code not listed here), while the C++ version is 788 bytes.