Neste artigo vamos apresentar um módulo bem interessante da ST que pode ser utilizado para a medição da distância absoluta de objetos e também integra um sensor de luz ambiente.

Este módulo utiliza a tecnologia FlightSense®, patenteada pela ST, que mede precisamente o tempo gasto pelo laser do VL6180x ser emitido, refletir no objeto mais próximo e voltar para o elemento foto-sensível presente no sensor (Time-of-Flight).

Desenho mecânico do VL6180x
Desenho mecânico do VL6180x. Imagem capturada do documento DocID028640 Rev 1.

Principais características:

Abaixo vou listar as principais características deste módulo:

Integração do VL6180x em seu projeto de sistema embarcado

Neste artigo vamos apresentar como podemos realizar a integração do sensor VL6180x em uma aplicação embarcada. Vamos demonstrar esta implementação utilizando como target o kit CY8CKIT-042 em conjunto com o kit de expansão X-NUCLEO-6180XA1. Essas duas placas possuem um header Arduino e, com um pouco de retrabalho, podemos utilizar essas placas em conjunto no nosso projeto. 

O kit CY8CKIT-042 é uma placa de avaliação para PSoC ARM Cortex-M0 da Cypress. É um ótimo kit de avaliação para entrar no mundo dos PSoC da Cypress. Ele é baseado no microcontrolador CY8C4245AXI-483 que dentre as principais características possui, 32 KB de flash, 4 KB de SRAM e alguns periféricos interessantes, como CapSense®, conversores AD, canais de comunicação I2C e UART, timers e etc.

Diagrama em blocos da familia PSoC 4200
Diagrama em blocos da familia PSoC 4200. Imagem capturada do documento 01-87197

Não vou entrar em muitos detalhes sobre os PSoC pois esses já foram abordados pelo articulista André Curvello em outros artigos presentes aqui no Embarcados.

Agora vamos falar um pouco sobre a placa de expansão X-NUCLEO-6180AX1. Ela possui um sensor VL6180x, expansão para três módulos satélite VL6180x (utilizado para gestures), um expansor de IO para controle do display e dos GPIOs das placas satélites.

Placa de expansão VL6180 conectada em uma STM32 Nucleo
Placa de expansão VL6180 conectada em uma STM32 Nucleo. Imagem capturada do documento DocID027330

Para uma fácil integração do VL6180x com qualquer sistema embarcado, a ST fornece uma API (Application Programming Interface) que permite uma fácil integração do sensor com a plataforma alvo. Esta biblioteca pode ser baixada a partir daqui

Alterações no hardware

Como nem sempre as configurações de hardware das placas que pretendemos utilizar casam com as nossas demandas de firmware, algumas vezes devemos realizar algumas alterações nas placas envolvidas. Para este projeto, tive que realizar as seguintes na X-NUCLEO-6180AX1 e na CY8CKIT-042:

A alteração mais necessária foi a relativa ao GPIO0. Olhando o esquema elétrico simplificado do kit, notei que para controlar o GPIO0 eu deveria controlar o expansor de IO e isso fugiria um pouco do escopo do projeto final.

Conexões X-Nucleo-6180AX1
Conexões da X-Nucleo-6180AX1

Visão geral da biblioteca VL6180X_API

Realizada as alterações, vamos para a integração da biblioteca VL6180X_API ao projeto. Após realizar o download e descompactar a biblioteca, reparamos que ela é organizada da seguinte forma:

Organização da API
Organização da API

O código fonte da API é estruturada em vários módulos definidos da seguinte forma:

O trabalho inicial é inserir uma pequena camada de abstração de hardware para que a biblioteca se comunique em nossa plataforma alvo. Para isso, vamos habilitar o I2C em nossa placa.

Integração da biblioteca ao nosso código fonte

Como descrito nos artigos do André Curvello, a habilitação de um periférico no PSoC Creator não apresenta muito segredos. Munido do datasheet (pois, como em qualquer outro microcontrolador, o PSoC tem um subset de pinos que podem ser atribuídos a cada periférico) escolhemos o mapeamento de IOs mais apropriado ao suporte de um canal I2C. Nas figuras abaixo mostro como está a configuração do nosso PSoC:

Componente SCB (Serial Communication Block) inserido no projeto
Componente SCB (Serial Communication Block) inserido no projeto e configurado como I2C Master com clock de 100 kHz
Mapeamento dos pinos de IO do nosso PSoC.
Mapeamento dos pinos de IO do nosso PSoC. Note que para o projeto que estou trabalhando realizei uma turbinada no microcontrolador e coloquei um CY8C4126AXI, que possue 64 kB de Flash, 8 kB de SRAM e é pino a pino compatível.

No projeto que estou trabalhando atualmente há dois módulos VL6180x presentes na placa como podemos ver na partícula de esquemático apresentada abaixo:

Partícula do esquemático do meu projeto atual com dois sensores VL6180X.
Partícula do esquemático do meu projeto atual. Note que há dois sensores VL6180X.

Vale enfatizar que nesta etapa de prototipagem estou trabalhando somente com um sensor VL6180x e, no desenvolvimento da solução, a minha intenção é que o código final já apresente suporte para N sensores. Esta etapa do desenvolvimento será abordada no decorrer do texto.

A próxima tarefa é implementar algumas funções de baixo nível da VL6180X_API para que seja possível habilitar a comunicação I2C. As funções que devemos implementar são as VL6180x_I2CWrite e VL6180x_I2CRead, presentes no módulo vl6180x_i2c.h . A implementação destas funções acabei colocando em um arquivo chamado vl6180x_hal.c :

/**
 *
 *  @file vl6180x_hal.c
 *  @brief implementation of functions that provide access to I2C interface with vl6180x
 *  @author Rafael Dias Menezes <rdmeneze@gmail.com>
 *  @date 2017/05
 */

#include "vl6180x_inc.h"
#include "vl6180x_i2c.h"

/********************************************************************************/
/* sensor definitions                                                           */
/* this field will be used on VL6180xDev_t->i2c_dev_addr */
/********************************************************************************/

int VL6180x_I2CWrite(VL6180xDev_t dev, uint8_t *buff, uint8_t len)
{
    uint32_t dwRet = EFAULT;
    
    if ( NULL != dev )
    {
        (void)xI2C_VL6180_I2CMasterWriteBuf( dev->i2c_dev_addr, 
                                             buff, 
                                             len, 
                                             xI2C_VL6180_I2C_MODE_COMPLETE_XFER );
        
        while (0u == (xI2C_VL6180_I2CMasterStatus() & xI2C_VL6180_I2C_MSTAT_WR_CMPLT));
        
        if (0u == (xI2C_VL6180_I2C_MSTAT_ERR_XFER & xI2C_VL6180_I2CMasterStatus()))
        {
            dwRet = EACCES;
            if (xI2C_VL6180_I2CMasterGetWriteBufSize() == len)
            {
                dwRet = 0;
            }
        }
    }
    
    return dwRet;
}

/******************************************************************************/

int VL6180x_I2CRead(VL6180xDev_t dev, uint8_t *buff, uint8_t len) 
{
    uint32 dwRet = EFAULT;    
    
    if ( NULL != dev )
    {
        (void)xI2C_VL6180_I2CMasterReadBuf( dev->i2c_dev_addr, 
                                            buff, 
                                            len, 
                                            xI2C_VL6180_I2C_MODE_COMPLETE_XFER );

        /* Waits until master complete read transfer */
        while (0u == (xI2C_VL6180_I2CMasterStatus() & xI2C_VL6180_I2C_MSTAT_RD_CMPLT));
        
        if (0u == (xI2C_VL6180_I2C_MSTAT_ERR_XFER & xI2C_VL6180_I2CMasterStatus()))
        {
            if ( xI2C_VL6180_I2CMasterGetReadBufSize() == len )
            {
                dwRet = 0;
            }
        }
    }    
    return dwRet;
}

/******************************************************************************/

Sendo que a definição do tipo VL6180xDev_t  é dada por:

struct MyDev_t {
    struct VL6180xDevData_t Data;          /*!< embed ST VL6180 Dev  data as "Data"*/
    //TODO ADD HERE any extra device data
    /*!< user specific field */
    int     i2c_bus_num;                   /*!< i2c bus number user specific field */
    int     i2c_dev_addr;                  /*!< i2c devcie address user specific field */
    mutex_t dev_lock   ;                   /*!< mutex user specific field */
    int     i2c_file;                      /*!< sample i2c file handle */
};
typedef struct MyDev_t *VL6180xDev_t;

A ideia do tipo VL6180xDev_t é que ele seja agnóstico de arquitetura, deixando a cargo do implementador que dê a melhor roupagem para a estrutura e que esta consiga descrever melhor a plataforma alvo.

Para o projeto que estou trabalhando, tenho como requisito a inserção de dois sensores VL6180x, como foi comentado anteriormente no texto. Para isso, resolvi partir para uma abordagem um pouco mais sofisticada na implementação do módulo de controle do sensor VL6180x. Para isso, redefini a estrutura MyDev_t , inserindo ponteiros para funções de controle de GPIO, inicialização de ISRs, inicialização do periférico e etc. Abaixo segue a nova “roupagem” dada à estrutura MyDev_t:

struct MyDev_t {
    struct VL6180xDevData_t Data;               /*!< embed ST VL6180 Dev  data as "Data"*/
    //TODO ADD HERE any extra device data
    /*!< user specific field */
    int         i2c_bus_num;                    /*!< i2c bus number user specific field */
    int         i2c_dev_addr;                   /*!< i2c device address user specific field */
    mutex_t     dev_lock   ;                    /*!< mutex user specific field */
    int         i2c_file;                       /*!< sample i2c file handle */
    TProxAlsID  xSensId;                        /*!< Sensor ID */
    TManufac    xManufac;                       /*!< Sensor Manufactor */
    uint8_t     bInit;
    TCurrentConvertion xCurConv;
    
    /* module gpios interface functions */
    void (*sleep)(void);                    /**!< enter in sleep mode                   */
    void (*wakeup)(void);                   /**!< wakeup from sleep mode                */
    void (*write_cs)( uint8_t value );      /**!< write chip select interface function  */
    void (*set_cs)(void);                   /**!< set the chip select                   */
    void (*reset_cs)(void);                 /**!< reset the  chip select                */
    uint8_t (*read_cs)( void );             /**!< read chip select interface function   */
    uint8_t (*read_st)( void );             /**!< read status interface function        */
    void (*isr_init)(void);                 /**!< initialize the interruption           */    
};

Já temos a definição dos objetos básicos de controle do VL6180x, agora vamos ter que definir as estruturas que realizam as tarefas de realizar a leitura dos sensores VL6180x. Para isso criei o módulo OpticalSensorInterface , que poderia, a princípio encapsular as funções de acesso aos sensores presentes na placa.

/**
 * @brief define the type of convertion that the VL6180X is performing now
 */
typedef enum 
{
    ST_ALS, 
    ST_RANGE, 
    ST_NONE,
} TCurrentConvertion;

    
//!  Interface type definition
typedef struct STOpticalSensorInt STOpticalSensorInt;

/**
 *  @struct STOpticalSensorInt
 *  @brief  a generic interface to optical sensor
 */
struct STOpticalSensorInt
{
    void*       dev;                                                                        /**! virtual device pointer     */
    uint32_t    (*init)(STOpticalSensorInt* _this);                                         /**! init function interface    */
    uint32_t    (*deinit)(STOpticalSensorInt* _this);                                       /**! init function interface    */
    
    uint32_t    (*getDistance)(STOpticalSensorInt*  _this, void* pRet, size_t szLen);       /**! get distance function interface */
    uint32_t    (*getAmbientLight)(STOpticalSensorInt*  _this, void* pRet, size_t szLen);   /**! get ambient light function interface */

    uint32_t    (*gettype)(STOpticalSensorInt* _this);
    
    uint32_t    (*getIsrCount)(STOpticalSensorInt* _this);                                   /**!< get the ISR occurrence counter        */

};

/**
 *  @brief  get a instance of the optical sensor struct defined by uiSensID
 *  @param[in] uiSensID sensor ID
 *  @returns NULL  in case of errors
 *  @returns valid pointer in case of success
 */    
STOpticalSensorInt* getOpticalSensorInstance( uint32_t uiSensID );

O ponto chave aqui é que o campo dev , que representa o dispositivo a ser integrado, pode representar qualquer tipo de sensor ótico. Com esta abstração podemos integrar “facilmente” qualquer outro sensor ótico que tenha características semelhantes. No nosso exemplo, acabei criando um simulacro de sensor ótico, que chamei de vl6180_sim, e que possue somente funções dummy. Temos aqui uma implementação em firmware do paradigma de polimorfismo, que morrerá quando a placa final chegar e não for mais necessário manter esses dois objetos para simular como seria a implementação completa. Na listagem a seguir apresento como é a inicialização das instâncias da estrutura STOpticalSensorInt presente em nosso projeto:

/****************************************************************************/
/* static members                                                           */
/* dev_vl6180x:  vector with the description of optical drivers installed on*/ 
/*  hardware                                                            */
/****************************************************************************/
static struct MyDev_t dev_vl6180x[] = 
{
    [0] = 
    {
        .i2c_dev_addr   = VL6180x_DEF_I2C_ADDR,     /*!< default I2C address */
        .xSensId        = SENS_0,                   /*!< sens ID             */
        .xManufac       = STMICRO,                  /*!< manufactor          */
        
        /*! device access functions */
        .write_cs       = vl6180_G00_Write,         /*!< write chip select pin      */
        .read_cs        = vl6180_G00_Read,          /*!< read chip select pin       */
        .read_st        = vl6180_G01_Read,          /*!< read the state of INT pin  */

        .set_cs         = vl6180_dev_en_set0,       /*!< set Chip Select                */
        .reset_cs       = vl6180_dev_en_reset0,     /*!< reset chip select              */
        .isr_init       = vl6180_dev_isr_init0,     /*!< initialize the interrupt GPIO  */
        
        .sleep          = vl6180_dev_sleep0,        /*!< enter in sleep mode            */
        .wakeup         = vl6180_dev_wakeup0,       /*!< wakeup                         */
        
    },
    [1] = 
    {
        .i2c_dev_addr   = VL6180x_DEF_I2C_ADDR,
        .xSensId        = SENS_1, 
        .xManufac       = SIMULATION, 
        
        .write_cs       = vl6180_sim_en_write,
        .read_cs        = vl6180_sim_en_read,
        .read_st        = vl6180_sim_st_read,
        
        .set_cs         = vl6180_sim_en_set,
        .reset_cs       = vl6180_sim_en_reset,
        .isr_init       = vl6180_sim_isr_init,

        .sleep          = vl6180_sim_sleep,
        .wakeup         = vl6180_sim_wakeup,        
        
    },
};


/****************************************************************************/
/*  oDev: class that give access to the optical sensor.                     */
/*      here, we have two objects: one related to the VL6180x and another   */
/*      related to a simulation instance                                    */
/****************************************************************************/
static STOpticalSensorInt oDev[] = 
{
    [0]=
    {
        .dev                = &dev_vl6180x[0],
        .init               = vl6180x_dev_init,
        .gettype            = vl6180x_dev_gettype,
        .deinit             = vl6180x_dev_deinit,
        .getDistance        = vl6180x_dev_getDistance,
        .getAmbientLight    = vl6180x_dev_getAmbientLight,
        .getIsrCount        = vl6180_dev_isr_count0,
    },
    [1]=
    {
        .dev                = &dev_vl6180x[1],
        .init               = dev_vl6180x_sim_init,
        .gettype            = dev_vl6180x_sim_gettype,
        .deinit             = dev_vl6180x_sim_deinit,
        .getDistance        = dev_vl6180x_sim_getDistance,
        .getAmbientLight    = dev_vl6180x_sim_getAmbientLight,
        .getIsrCount        = vl6180_sim_isr_count, 
    },
};

A solução de baixo nível para o acesso aos sensores óticos presentes na placa foi implementada no módulo dev_VL6180x.c , ilustrado abaixo:

/****************************************************************************/
/* module defines                                                           */
#define VL6180x_CS_HIGH 1
#define VL6180x_CS_LOW  0

#define VL6180X_DEFAULT_PRIORITY 3L

#define VL6180x_DEVICE_IDENTIFICATION   (0xB4)

#define ALS_INTER_MEASUREMENT_TIME      (100L)
#define ALS_INTEGRATION_TIME            (50L)
#define RANGE_MAX_CONV_TIME             (30L)
#define RANGE_INTER_MEASUREMENT_TIME    (100L)

/****************************************************************************/
/* local defines                                                            */
static      BOOL bInitI2c           = FALSE;
volatile    BOOL bIsrOccurred[2]    = {FALSE};
volatile    uint32_t bIsrCounter[2] = {0};

/****************************************************************************/

void vl6180_dev_en_set0( void )
{
    vl6180_G00_Write( VL6180x_CS_HIGH );
}

/****************************************************************************/

void vl6180_dev_en_reset0( void )
{
    vl6180_G00_Write( VL6180x_CS_LOW );
}

/****************************************************************************/

void vl6180_dev_en_set1( void )
{
    vl6180_G10_Write( VL6180x_CS_HIGH );
}

/****************************************************************************/

void vl6180_dev_en_reset1( void )
{
    vl6180_G10_Write( VL6180x_CS_LOW );
}

/****************************************************************************/

static void isr_gpio10( void )
{
    bIsrOccurred[0] = TRUE;
    bIsrCounter[0]++;
    
    isr_G10_ClearPending();
    
    vl6180_G10_ClearInterrupt();    
}

/****************************************************************************/

static void isr_gpio11( void )
{
    bIsrOccurred[1] = TRUE;
    bIsrCounter[1]++;
    
    isr_G11_ClearPending();
    
    vl6180_G11_ClearInterrupt();        
}

/****************************************************************************/

void vl6180_dev_isr_init0( void )
{
    isr_G10_StartEx( isr_gpio10 );
    
    isr_G10_SetPriority( VL6180X_DEFAULT_PRIORITY );
}

/****************************************************************************/

void vl6180_dev_isr_init1( void )
{
    isr_G11_StartEx( isr_gpio11 );
    
    isr_G11_SetPriority( VL6180X_DEFAULT_PRIORITY );    
}

/****************************************************************************/

uint32_t vl6180_dev_isr_count0( STOpticalSensorInt* _this )
{
    (void)_this;
    return (uint32_t)bIsrCounter[0];
}

/****************************************************************************/

uint32_t vl6180_dev_isr_count1( STOpticalSensorInt* _this )
{
    (void)_this;
    return (uint32_t)bIsrCounter[1];
}

/****************************************************************************/

void vl6180_dev_sleep0(void)
{
    vl6180_dev_en_reset0();

    xI2C_VL6180_sda_SetDriveMode( xI2C_VL6180_sda_DM_DIG_HIZ );
    xI2C_VL6180_scl_SetDriveMode( xI2C_VL6180_scl_DM_DIG_HIZ );
    
    xI2C_VL6180_Sleep();
    
    vl6180x_dev_deinit( NULL );
    
    return;
}

/****************************************************************************/

void vl6180_dev_wakeup0(void)
{
    
    /* to be implemented */
    
    return;
}

/****************************************************************************/

void vl6180_dev_sleep1(void)
{
    vl6180_dev_en_reset1();

    xI2C_VL6180_sda_SetDriveMode( xI2C_VL6180_sda_DM_DIG_HIZ );
    xI2C_VL6180_scl_SetDriveMode( xI2C_VL6180_scl_DM_DIG_HIZ );
    
    xI2C_VL6180_Sleep();
    
    vl6180x_dev_deinit( NULL );
    
    return;
}

/****************************************************************************/

void vl6180_dev_wakeup1(void)
{
    return;
}

/****************************************************************************/

uint32_t vl6180x_dev_init_interleaved( struct MyDev_t* dev )
{
    uint32_t dwRet = 0;
    int iRet;
    
    if ( NULL == dev )
    {
        dwRet = ENOMEM;
        goto VL6180X_init_interleaved_error;
    }
    
    
    /* next commands are related to the design tip DT0017 */
    /*                                                    */
    /*                                                    */   
    dwRet = EIO;
    iRet = VL6180x_AlsSetIntegrationPeriod( dev, ALS_INTEGRATION_TIME );
    
    dwRet = EIO;                    
    iRet = VL6180x_RangeSetMaxConvergenceTime( dev, RANGE_MAX_CONV_TIME );
    if ( iRet ) goto VL6180X_init_interleaved_error;    
    
    dwRet = EIO;                    
    iRet = VL6180x_RangeSetInterMeasPeriod( dev, RANGE_INTER_MEASUREMENT_TIME );
    if ( iRet ) goto VL6180X_init_interleaved_error;    
  
    dwRet = EIO;                    
    iRet = VL6180x_AlsSetInterMeasurementPeriod( dev, ALS_INTER_MEASUREMENT_TIME );
    if ( iRet ) goto VL6180X_init_interleaved_error;     
    
    dwRet = EIO;                    
    iRet = VL6180x_AlsSetInterleavedMode( dev, INTERLEAVED_ENABLE );
    if ( iRet ) goto VL6180X_init_interleaved_error;  
    
    dwRet = EIO;
    iRet = VL6180x_AlsSetSystemMode( dev, MODE_CONTINUOUS | MODE_START_STOP );
    if ( iRet ) goto VL6180X_init_interleaved_error;
    
    dwRet = 0;
    
    goto VL6180X_init_interleaved_OK;
    
VL6180X_init_interleaved_error:

VL6180X_init_interleaved_OK: 
    return dwRet;
    
}

/****************************************************************************/

uint32_t    vl6180x_dev_init(STOpticalSensorInt* _this)
{
    uint32_t dwRet = EFAULT;
    if ( NULL != _this )
    {
        if ( NULL != _this->dev )
        {
            struct MyDev_t* dev = (struct MyDev_t*)_this->dev;
            
            if ( FALSE == dev->bInit )
            {
                int iRet;
                uint8_t bIdModel;                

                
                /* VL6180x initialization process */
                dev->xCurConv = ST_ALS;
                
                dev->reset_cs( );
                
                CyDelayUs( 500 );  /* wait for 500 us */
                
                dev->set_cs( );
                
                CyDelay( 10 );  /* wait for 10 ms */

                VL6180x_FilterSetState( dev, 0 );
                
                if ( FALSE == bInitI2c )
                {
                    // initialize the PSoC I2C port
                    xI2C_VL6180_Start();
                    bInitI2c = TRUE;
                }
                
                dwRet = ENOMEDIUM;
                VL6180x_RdByte( dev, IDENTIFICATION_MODEL_ID, &bIdModel );
                if ( bIdModel != VL6180x_DEVICE_IDENTIFICATION ) goto VL6180X_init_error;
                
                CyDelayUs( 1000 );
                
                dwRet = EIO;
                iRet = VL6180x_WrByte( dev, SYSTEM_FRESH_OUT_OF_RESET, 1 );
                if (iRet) goto VL6180X_init_error;                
                
                VL6180x_WaitDeviceBooted( dev );                       

                dwRet = EDEADLK;
                iRet = VL6180x_InitData( dev );
                if (iRet) goto VL6180X_init_error;
                
                dwRet = ENOSPC;
                iRet = VL6180x_Prepare( dev );
                if (iRet) goto VL6180X_init_error;
                
                dwRet = EIO;                    
                iRet = VL6180x_AlsConfigInterrupt( dev, CONFIG_GPIO_INTERRUPT_NEW_SAMPLE_READY );
                if (iRet) goto VL6180X_init_error;
                
                dwRet = EIO;
                iRet = VL6180x_RangeConfigInterrupt( dev, CONFIG_GPIO_INTERRUPT_NEW_SAMPLE_READY );
                if (iRet) goto VL6180X_init_error;
                
                dwRet = EIO;                    
                iRet = VL6180x_SetupGPIO1( dev, GPIOx_SELECT_GPIO_INTERRUPT_OUTPUT, INTR_POL_HIGH ); 
                if ( iRet ) goto VL6180X_init_error;        
                
                /* next commands are related to the design tip DT0017 */
                /*                                                    */
                /*                                                    */
                dwRet = EIO;
                iRet = vl6180x_dev_init_interleaved( dev );
                if ( iRet ) goto VL6180X_init_error;     
                
                dwRet = EIO;
                iRet = VL6180x_AlsSetAnalogueGain( dev, 7 );
                if ( iRet ) goto VL6180X_init_error;     
                
                dev->isr_init( );
                
                /*                                                    */
                /*                                                    */
                /******************************************************/
                /* initialization OK */
                dev->bInit = TRUE;
                dwRet = 0;   
                
                goto VL6180X_init_OK;
            }
        }
    }
    
VL6180X_init_error:
    
VL6180X_init_OK:
    return dwRet;
}

/****************************************************************************/

uint32_t    vl6180x_dev_deinit(STOpticalSensorInt* _this)
{
    (void)_this;
    
    bInitI2c = FALSE;
    return 0;
}


/****************************************************************************/

uint32_t    vl6180x_dev_getDistance(STOpticalSensorInt*  _this, void* pRet, size_t szLen)
{
    uint32_t dwRet = EFAULT;
    int iRet;
    static VL6180x_RangeData_t xRangeData;
    uint8_t xRangeData_intStatus;
    
    if ( NULL != _this )
    {
        if ( NULL != pRet )
        {
            dwRet = 0;            
            struct MyDev_t* dev = (struct MyDev_t*)_this->dev;
            if ( ST_RANGE == dev->xCurConv )
            {
                
                
                    (void)VL6180x_RangeGetInterruptStatus( dev, &xRangeData_intStatus );
                    if ( (RESULT_INTERRUPT_STATUS_GPIO & xRangeData_intStatus) == RES_INT_STAT_GPIO_NEW_SAMPLE_READY )
                    {
                        dwRet = EFAULT;
                        iRet = VL6180x_RangeGetMeasurement(dev, &xRangeData);
                        if ( 0 == iRet ) 
                        {                
                            do
                            {
                                VL6180x_ClearAllInterrupt( dev );
                                (void)VL6180x_RangeGetInterruptStatus( dev, &xRangeData_intStatus );
                            }while( (RESULT_INTERRUPT_STATUS_GPIO & xRangeData_intStatus) == RES_INT_STAT_GPIO_NEW_SAMPLE_READY );
                            
                            {
                                if ( sizeof( uint32_t) == szLen )
                                {
                                    if ( NULL != pRet )
                                    {
                                        *(uint32_t*)pRet = xRangeData.FilteredData.rawRange_mm;
                                    }
                                }                                                                                            
                            }                            
                        }
                    }
                    
                    bIsrOccurred[dev->xSensId] = FALSE;
                    dev->xCurConv = ST_ALS;
                    dwRet = 0;                                                                                
                
            }
            dwRet = 0;
        }
    }
    
    return dwRet;
}

/****************************************************************************/

uint32_t    vl6180x_dev_getAmbientLight(STOpticalSensorInt*  _this, void* pRet, size_t szLen)
{
    int iRet;    
    uint32_t dwRet = EFAULT;
    uint8_t xAlsData_intStatus;
    static VL6180x_AlsData_t xVL6180x_AlsData;
    
    if ( NULL != _this )
    {
        if ( NULL != pRet )
        {
            dwRet = 0;
            struct MyDev_t* dev = (struct MyDev_t*)_this->dev;
            if ( ST_ALS == dev->xCurConv )
            {
                                   
                    (void)VL6180x_AlsGetInterruptStatus( dev, &xAlsData_intStatus ); 
                    if ( (RESULT_INTERRUPT_STATUS_GPIO & xAlsData_intStatus) == RES_INT_STAT_GPIO_NEW_SAMPLE_READY )
                    {
                        dwRet = EFAULT;
                        iRet = VL6180x_AlsGetMeasurement( dev, &xVL6180x_AlsData );
                        if ( !iRet )
                        {
                            do
                            {
                                VL6180x_ClearAllInterrupt( dev );
                                (void)VL6180x_AlsGetInterruptStatus( dev, &xAlsData_intStatus );
                            }while( (RESULT_INTERRUPT_STATUS_GPIO & xAlsData_intStatus) == RES_INT_STAT_GPIO_NEW_SAMPLE_READY );
                            
                            {
                                if ( sizeof( uint32_t) == szLen )
                                {
                                    if ( NULL != pRet )
                                    {
                                        *(uint32_t*)pRet = xVL6180x_AlsData.lux;
                                    }
                                }                                                                                            
                            }
                        }  
                    }
                    
                    bIsrOccurred[dev->xSensId] = FALSE;
                    dev->xCurConv = ST_RANGE;
                    dwRet = 0;                    
                                
            }
        }
    }
    
    return dwRet;
}

/****************************************************************************/

uint32_t vl6180x_dev_gettype(STOpticalSensorInt*  _this)
{
    uint32_t dwRet = -1;
    if ( NULL != _this )
    {
        struct MyDev_t* dev = (struct MyDev_t*)_this->dev;
        if ( NULL != dev )
        {
            dwRet = (uint32_t)dev->xManufac;
        }
    }
    
    return dwRet;
}

/****************************************************************************/

Note que a inicialização é realizada através da chamada da rotina vl6180x_dev_init . Internamente, a vl6180x_dev_init consegue resolver qual dispositivo será controlado. Isso porque esta rotina recebe como parâmetro um ponteiro para uma estrutura STOpticalSensorInt que, por sua vez, possui um ponteiro para o dispositivo a ser inicializado. Neste ponto temos uma bela abstração do tipo de dispositivo que está sendo utilizado, podendo ser de qualquer tipo desde que respeite a interface dada pela estrutura MyDev_t .

A inicialização do sensor VL6180x é realizada para que ele trabalhe no modo intercalado, onde o sensor inicia a aquisição da luz ambiente e, assim que esta é finalizada, o mesmo já inicia um processo de aquisição do sensor de proximidade. No final de cada passo, ele deveria gerar uma interrupção via GPIO1, porém este evento não está ocorrendo. Por isso que o firmware na versão atual realiza o polling do status do registrador de interrupção através da chamada das funções VL6180x_AlsGetInterruptStatus e VL6180x_RangeGetInterruptStatus .

Para ter acesso às leituras dos sensores, vamos criar um módulo Sens como exemplo:

#include "OpticalSensorInterface.h"
#include "errno.h"
#include "TTimer.h"

/************************************************************************************************/
#define SENS_SCAN_TIME (1 * TTIMER_1MS_INTERVAL)

/************************************************************************************************/
/**
 *  @brief optical sensor virtual interface
 */
static STOpticalSensorInt*  xOpticalSens[] = 
{
    NULL, 
    NULL
};

static uint32_t dwOpticalTaskTimerHandle    = -1;  /*!< timer handle to the optical refresh task **/
static BOOL     bOpticalTaskSignal          = FALSE;     /*!< signal **/
static uint32_t dwDistance[2]               = {0};
static uint32_t dwAmbientLight[2]           = {0};
static uint32_t dwSignalStrenght[2]         = {0};


/************************************************************************************************/

static uint32_t OpticalTaskTimer( void* lpParam )
{
    (void)lpParam;
    
    bOpticalTaskSignal = TRUE;
    
    return 0;
}

/************************************************************************************************/

uint32_t SensInit( void )
{ 
    uint32_t dwRet = 0;
    int i;
    for( i = 0 ; i < GET_ARRAY_LEN(xOpticalSens) ; i++ )
    {
        xOpticalSens[i] = getOpticalSensorInstance( i );
        if ( NULL == xOpticalSens[i] )
        {
            dwRet = ECHRNG;
            break;
        }
        
        dwRet = xOpticalSens[i]->init( xOpticalSens[i] );
        if ( dwRet )
        {
            dwRet = ENXIO;
            break;
        }
    }
    
    if ( !dwRet )
    {
        dwRet = TTimerRegisterCallBack( SENS_SCAN_TIME_SCAN_TIME, 
                                        TimerPeriodic, 
                                        OpticalTaskTimer, 
                                        NULL, 
                                        &dwOpticalTaskTimerHandle );
    }
    else
    {
        if ( GET_ARRAY_LEN( xOpticalSens ) != i ) 
        {
            dwRet = ENODEV;
        }
    }
    
    if ( !dwRet )
    {
        TTimerStart( dwOpticalTaskTimerHandle );
    }
    
    return dwRet;
}

/************************************************************************************************/

void doSensProcessing( void )
{
    uint32_t dwRet;
    static uint8_t bOpticalSensorCounter = 0;
    
    if ( bOpticalTaskSignal )
    {
        // get the ambient light
        dwRet = xOpticalSens[bOpticalSensorCounter]->getAmbientLight(   xOpticalSens[bOpticalSensorCounter], 
                                                                        &dwAmbientLight[bOpticalSensorCounter], 
                                                                        sizeof( uint32_t ));
        if ( !dwRet )
        {
            // get the distance
            dwRet = xOpticalSens[bOpticalSensorCounter]->getDistance(   xOpticalSens[bOpticalSensorCounter], 
                                                                        &dwDistance[bOpticalSensorCounter], 
                                                                        sizeof( uint32_t ));
        }
        
        bOpticalSensorCounter++;
        if ( GET_ARRAY_LEN(xOpticalSens) == bOpticalSensorCounter )
        {
            bOpticalSensorCounter = 0;
        }
        
        bOpticalTaskSignal = FALSE;
    }
}

/************************************************************************************************/

uint32_t SensGetNumberOfSensors( void )
{
    return GET_ARRAY_LEN(xOpticalSens);
}

/************************************************************************************************/

uint32_t SensGetSensorType( uint8_t bSensId )
{
    uint32_t dwRet = -1;
    if ( bSensId < SensGetNumberOfSensors() )
    {
        dwRet = xOpticalSens[bSensId]->gettype( xOpticalSens[bSensId] );
    }
    
    return dwRet;
}

/************************************************************************************************/

uint32_t SensGetAmbientLight( uint8_t bSensId, uint32_t* pAlsValue )
{
    uint32_t dwRet = -1;
    if ( bSensId < SensGetNumberOfSensors() )
    {
        if ( NULL != pAlsValue )
        {
            *pAlsValue = dwAmbientLight[bSensId];          
            dwRet = 0;
        }
    }
    
    return dwRet;    
}

/************************************************************************************************/

uint32_t SensGetDistance( uint8_t bSensId, uint32_t* pRangeValue )
{
    uint32_t dwRet = -1;
    if ( bSensId < SensGetNumberOfSensors() )
    {
        if ( NULL != pRangeValue )
        {
            *pRangeValue = dwDistance[bSensId];           
            dwRet = 0;
        }
    }
    
    return dwRet;    
}

/************************************************************************************************/

uint32_t SensGetIsrCount( uint8_t bSensId, uint32_t* pIsrCount )
{
    uint32_t dwRet = -1;
    if ( bSensId < SensGetNumberOfSensors() )
    {
        if ( NULL != pIsrCount )
        {
            *pIsrCount = xOpticalSens[bSensId]->getIsrCount( xOpticalSens[bSensId] );           
            dwRet = 0;
        }
    }
    
    return dwRet;    
    
}

/************************************************************************************************/

Observe que, por exemplo, ao se chamar o método xOpticalSens[i]->init( xOpticalSens[i] ), não estamos nos importando muito com qual tipo de dispositivo está sendo inicializado. O próprio objeto xOpticalSens possui toda a informação necessária para que ele saiba como deve ser inicializado. O mesmo ocorre para todos os outros métodos presentes na estrutura STOpticalSensorInt. Podemos integrar o módulo Sens à rotina main da seguinte forma:

void main( void )
{
	char sResult[128];
        uint32_t dwRet;
	uint32_t dwResultALS;
	uint32_t dwResultRange;
	

	dwRet = SensInit( );
	if ( dwRet ) while( 1 );
	
	for( ;; )
	{
	    doSensProcessing();
		
		SensGetAmbientLight( 0, &dwResultALS );
		SensGetDistance( 0, &dwResultRange);
		
		sprintf( sResult, "ALS = %d, Range = %d", dwResultALS, dwResultRange );
		printf( sResult );
	}
	
	return;
}

A abstração fugiu do controle, não é?

Bom, realmente é um pouco difícil de visualizar de início, mas assim que vai observando como os objetos estão relacionados dá para se entender o poder da abstração empregada.

Para mostrar um resultado prático, abaixo mostro uma saída no terminal gerado pela implementação na qual estou trabalhando.

Resultados: Leitura da luz ambiente (ALS) e da proximidade (Range) em uma aplicação de terminal.
Resultados: Leitura da luz ambiente (ALS) e da proximidade (Range) em uma aplicação de terminal.

O repositório com a biblioteca utilizada no artigo pode ser encontrado neste github. Para realizar o port para algum outro microcontrolador, é só reescrever as rotinas VL6180x_I2CWrite e VL6180x_I2CRead. Enjoy! 🙂

Considerações finais

Neste artigo realizamos a integração de um sensor ótico VL6180X e, através de uma certa abstração, criamos uma arquitetura que nos possibilita colocar vários sensores óticos pendurados em uma solução de hardware qualquer. A ideia aqui não era a de abordar conceitos de programação orientada a objetos, mas acabamos caindo nesta área, que pode ser um pouco difícil para o desenvolvedor iniciante.

Agora, como você faria para implementar uma solução que fosse independente de placa? Se estamos desenvolvendo para uma placa que ainda está em desenvolvimento e precisamos já ir testando algumas coisas, como fazemos para que a transição seja a menos traumática possível?

Saiba mais sobre os assuntos abordados no artigo: