Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Dave Clarke
Published © CC BY-NC-SA

Talking iBreathe Breathalyzer - Hexiware

Utilizing the awesome Hexiware IoT dev kit, docking station and an Alcohol click board to create a fun IoT Breathalyzer that can talk to you

AdvancedFull instructions provided2 hours2,638

Things used in this project

Hardware components

Hexiwear
NXP Hexiwear
×1
MIKROE Hexiware Docking Station
×1
MIKROE Alcohol Click Board
×1
Super Glue
×1
M3 countersink Screw
×3
Resistor 10k ohm
Resistor 10k ohm
×3
Polymaker Polywood PLA
×1
Polymaker PolyPlus PLA (White)
×1
MIKROE Text to Speech Click Board
×1
1" 8ohm speaker
×1
3.5mm Headphone Jack
×1

Software apps and online services

ARM mBed OS 5
Xcode
Apple Xcode
Fusion
Autodesk Fusion

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

iBreathe Breathalyzer

Here's the Fusion360 model for the enclosure

iBreathe Assembly Parts

A ballooned Drawing showing the different components

iBreathe Breathalyzer 3D Print STL

Use these files to 3 print your enclosure

Schematics

Alcohol Click Board 3V3 modification

Use this to help an Alcohol Click Board behave nicely when using a Hexiware with mBed OS

Code

Talking iBreathe Breathalyzer for quick reference

C/C++
/****************************************************************************
* Title                 :   iBreathe Breathalyzer
* Filename              :   Talking_breathalyzer
* Author                :   Dave Clarke
* Origin Date           :   27/09/2016
* Notes                 :   Breathalyzer utilizing Hexiware, Alcohol click and 
                            Wolksense. Now with added voice thanks to the 
                            Text-To-Speech Click board
*****************************************************************************/
/**************************CHANGE LIST **************************************
*
*    Date    Software Version    Initials       Description
*  07/10/16       1.0.0            DC        Interface Created.
*
*****************************************************************************/

/**
 * @page TEST_CFG Test Configurations
 * <h3> Test configuration : </h3>
 * @par
 * <ul>
 * <li><b> MCU           </b> :      MK64FN1M0XXX12               </li>
 * <li><b> Dev. Board    </b> :      HEXIWEAR                     </li>
 * <li><b> Oscillator    </b> :      12 MHz external              </li>
 * <li><b> Ext. Modules  </b> :      Alcohol Click on mikroBUS 1  </li>
 * <li><b> Ext. Modules  </b> :      TTS Click on mikroBUS 2      </li>
 * <li><b> SW            </b> :      mBed OS5    </li>
 * </ul>
 */

/**
 * @mainpage
 * <h3> Breathalyser created with HEXIWEAR and mBed OS 5 </h3>
 * @par This will show you how much you've drunk and tell you with an Emoticon if
 * you're too hammered to even consider driving. Using the Hexiware app the readings
 * are transmitted to the cloud via bluetooth. Is it time to give up drinking yet?!
 * It now talks to you! 
 
 * <h3> Alcohol Features </h3>
 * @par Text to Speech Click, Alcohol click, Hexiwear docking station, Hexiware
 */

/******************************************************************************
* Includes
*******************************************************************************/

#include "mbed.h"
#include "Hexi_KW40Z.h"
#include "Hexi_OLED_SSD1351.h"
#include "OLED_types.h"
#include "OpenSans_Font.h"
#include "string.h"
#include "iBreatheImages.h"
#include "text_to_speech.h"
#include "text_to_speech_hal.h"
#include "text_to_speech_hw.h"
#include "text_to_speech_img.h"


/******************************************************************************
* Module Variable Definitions
*******************************************************************************/

#define LED_ON      0
#define LED_OFF     1

                          // Pointers to:  
const uint8_t   *welcome, // Welcome screen image
                *blank,   // blank image
                *blow,    // Start Blowing Image
                *drink,   // You've been drinking image
                *drive,   // Don't drive image
                *hang,    // You'll have a hangover image
                *ini,     // Initialising image
                *sober;   // Sober as a judge image

const float     Vadc_3V3  = 0.00005035;    // 16-Bit ADC step 3V3/65535   = 0.05035 mV
       
float           Vrl = 0,                   // Output voltage
                ambientAlc = 0,            // Abmient Output voltage from sensor
                SensorRes = 0,             // SensorRes (Ohm) - Sensor resistance
                SensorRes1 = 0,            // SensorRes (Ohm) - Sensor resistance
                ppm = 0,                   // ppm
                ppm_1 = 0,                 // Ambient ppm variable  
                ratio = 0;                 // SensorRes/LoadRes ratio
              
unsigned short  adc_rd = 0; //Initialise anologue read variable

const uint8_t   ppmText[] = "ppm:"; // text for PPM label

char            text[20],           // Text array variables
                text2[20],
                text3[20];

float           value[20],  // initial sensor set up values
                value1[20]; // initial sensor set up values

bool            isFirstBoot = true;                   
              

/* Indication Flags */
static volatile bool        _tts_rdy_f;
static volatile bool        _spc_rdy_f;
static volatile bool        _tts_fin_f;
static volatile bool        _spc_fin_f;

/* Error Buffers */
static uint16_t             _req_err;
static uint16_t             _err_code;

/* Default Configuration */
static ACONF_t              _audio_conf;
static TTSCONF_t            _tts_conf;
static PMANCONF_t           _pman_conf;

static bool                 _flush_enable;

/* Timer flag and counter */
static volatile bool        _ticker_f;
static volatile uint16_t    _ticker;

/* Input and output buffers */
static  ISC_REQ_t   _last_req;
static  ISC_RESP_t  _last_rsp;

static uint8_t test[ 8 ] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
int frequency = 750000;
VER_t *version;
/******************************************************************************
* Function Prototypes
*******************************************************************************/
/* Checks for indications */
static int _parse_ind( void );

/* Message block and error callback function pointers */
static void ( *_fatal_err_callback )( uint16_t *err_code );
static void ( *_err_callback )( uint16_t *err_code );
static void ( *_msg_block_callback )( uint16_t *msg_code,
                                      uint16_t *err_code ); 

void voiceinit(void);
void sysinit(void);
void ReadSensor();
void CalculatePPM(int times, bool amb);
void ambient(int times);
void StartHaptic(void);
void StopHaptic(void const *n);
void ButtonUp(void);
void txTask(void);
void fatal_err( uint16_t *err );
void msg_blk( uint16_t *req, uint16_t *err );

/******************************************************************************
* Instance setups
*******************************************************************************/


/* Define timer for haptic feedback */
RtosTimer hapticTimer(StopHaptic, osTimerOnce);

//set up Analog read pin for alcohol sensor
AnalogIn Alcohol(PTB2);

/* Instantiate the SSD1351 OLED Driver */ 
SSD1351 oled(PTB22,PTB21,PTC13,PTB20,PTE6, PTD15); /* (MOSI,SCLK,POWER,CS,RST,DC) */
      
/* Get OLED Class Default Text Properties */
oled_text_properties_t textProperties = {0};

/* Instantiate the Hexi KW40Z Driver (UART TX, UART RX) */ 
KW40Z kw40z_device(PTE24, PTE25);

/* LED and Haptic Set ups */
DigitalOut redLed(LED1);
DigitalOut greenLed(LED2);
DigitalOut blueLed(LED3);
DigitalOut haptic(PTB9);

/* Instantiate the Click Text to Speech pinout */ 
DigitalOut TTS_RST(PTB19);
DigitalOut TTS_CS(PTC3);
DigitalOut TTS_MUTE(PTB3);
DigitalIn  TTS_RDY(PTB8);

//Instantiate SPI for comms with Speak module
SPI TextToSpeech(PTC6, PTC7, PTC5); // MOSI, MISO, SCK

// Debug Serial
Serial pc(USBTX, USBRX);

/******************************************************************************
* Bluetooth button functions and passkey function
*******************************************************************************/

void ButtonRight(void)
{
    StartHaptic();
    kw40z_device.ToggleAdvertisementMode();
    blueLed = kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    redLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    greenLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
}

void ButtonLeft(void)
{
    StartHaptic();
    kw40z_device.ToggleAdvertisementMode();
    blueLed = kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    redLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    greenLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
}

void PassKey(void)
{
    StartHaptic();
    strcpy((char *) text,"PAIR CODE");
    oled.TextBox((uint8_t *)text,0,25,95,18);
  
    /* Display Bond Pass Key in a 95px by 18px textbox at x=0,y=40 */
    sprintf(text3,"%d", kw40z_device.GetPassKey());
    oled.TextBox((uint8_t *)text3,0,40,95,18);
}

/******************************************************************************
* Main
*******************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main()
{
   
     
     /* Set pointers to the BMPs stored in  iBreatheImages.c */
     welcome = iBreatheWS_bmp;    // Welcome screen image 
     blank = iBreatheBlank_bmp;   // blank image
     blow = iBreatheBlow_bmp;    // Start Blowing Image
     drink = iBreatheDrink_bmp;   // You've been drinking image
     drive = iBreatheDrive_bmp;   // Don't drive image
     hang = iBreatheHang_bmp;    // You'll have a hangover image
     ini = iBreatheini_bmp;     // Initialising image
     sober = iBreatheSober_bmp;   // Sober as a judge image
     
    /* Set initial Values */
     sysinit(); 
    
    /* initialise voice engine */  
     voiceinit();
     
    /* Welcome message on reset */    
     tts_speak( "Hello, welcome to the eye brethalizer " );
     wait(0.5);
     tts_speak( "please press start.  " ); 
}

/******************************************************************************
* Public Function Definitions
*******************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function readSensor(void)
* -------------------------------------------------------------------------------------------------
* Overview: Read sensor
* Input: None
* Output: None
**************************************************************************************************/

void ReadSensor()
{
    /* Read 16 Bit Analog value */
    adc_rd = Alcohol.read_u16();

    // pause 200ms 
    Thread::wait(200);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function CalculatePPM(Int times, bool amb)
* -------------------------------------------------------------------------------------------------
* Overview: Calculation of PPM
* Input: times = Number of samples to take, amb sets either ambient reading or test reading (true for test)
* Output: None
**************************************************************************************************/

void CalculatePPM(int times, bool amb)
{
        float   lgPPM;
        
        /* Read values x times */
        for(int x = 0; x < times; x++)
        {
            ReadSensor();    
            value[x] = ((float)adc_rd  * Vadc_3V3);
            StartHaptic();
            Thread::wait(50);
        }    
        
        /* Calculate the average value for accuratcy */
        for(int y = 0; y < times; y++)
        {
            Vrl += value[y];
        }
        Vrl = Vrl / times;       

        /* Set SensorRes reference value */
        SensorRes =    (Vrl / 3.3);
        
        /* Set ratio */
        ratio     = SensorRes1 / SensorRes;

        /* Convert to PPM */
        lgPPM = ( log10( ratio ) * -1.5512 ) + 2.5911;
        
        /* If true create test result, flase creates reference result */
        if (amb == true)
        {
            ppm = pow( 10, lgPPM );
        }
        else
        {
            ppm_1 = pow( 10, lgPPM );
        }    
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void ambient(int times)
* -------------------------------------------------------------------------------------------------
* Overview: Reading of the Ambient Voltgage of the sensor for reference
* Input: times = Number of samples to take
* Output: None
**************************************************************************************************/

void ambient(int times)
{
    /* Read ambient values x times flashing green led*/
    for(int x = 0; x < times; x++)
    {
        redLed      = LED_OFF;
        greenLed    = LED_OFF;
        blueLed     = LED_OFF;
        
        ReadSensor();
        value1[x] = (float)adc_rd  * Vadc_3V3; 
        
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_OFF;
        Thread::wait(48);   
    }
    
    /* Calculate the average value for accuratcy */
    for(int y = 0; y < times; y++)
    {
        ambientAlc+=value1[y];            
    }
    ambientAlc = ambientAlc / times;
  
    /* Set SensorRes1 reference value */
    SensorRes1 =   (ambientAlc / 3.3);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void StartHaptic(void)
* -------------------------------------------------------------------------------------------------
* Overview: Start Buzzing haptic motor
* Input:  None
* Output: None
**************************************************************************************************/

void StartHaptic(void)
{
    hapticTimer.start(50);
    haptic = 1;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void StopHaptic(void)
* -------------------------------------------------------------------------------------------------
* Overview: Stop Buzzing haptic motor
* Input:  None
* Output: None
**************************************************************************************************/

void StopHaptic(void const *n) {
    haptic = 0;
    hapticTimer.stop();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void ButtonUp(void)
* -------------------------------------------------------------------------------------------------
* Overview: Function called whe up button pressed, Begins Breathilyzer testing procedure
* Input:  None
* Output: None
**************************************************************************************************/

void ButtonUp(void)
{
    StartHaptic();
    
    bool Ref = false;
    bool Test = true;
        
    /* LED set to green for test beginning*/
    redLed      = LED_OFF;
    greenLed    = LED_ON;
    blueLed     = LED_OFF;
    
    /* Fill 96px by 96px Screen with 96px by 96px Initialising Image starting at x=0,y=0 */   
    oled.DrawImage(ini,0,0);
    
    /* first boot bug work around to stop junk values */
    if (isFirstBoot == true)
    {
        /*read ambient atmosphere levels with 10 samples and set flag to show it's ambient basline figure*/
        ambient(1);
        CalculatePPM(1,Ref);
        isFirstBoot = false;
    }
    
    
    /*read ambient atmosphere levels with 10 samples and set flag to show it's ambient basline figure*/
    tts_speak( "initializing" );
    ambient(10);
    CalculatePPM(10,Ref);
    
    
    /* Fill 96px by 96px Screen with 96px by 96px Blowing Image starting at x=0,y=0 */  
    oled.DrawImage(blow,0,0);
    tts_speak( "please blow until buzzing stops" );
    /*read breathe alcohol levels with 10 samples and set flag to show it's breathilyzer test figure*/
    CalculatePPM(10,Test);
    
    /*Calculate the difference in Alcohol level based on Ambient and test sample*/
    ppm = ppm - ppm_1;
    
    /*Throw away any values less than 0*/
    if (ppm < 0)
    {
         ppm = 0;
    }     
    
    /* Fill 96px by 96px Screen with 96px by 96px Blank Background Image starting at x=0,y=0 */
    oled.DrawImage(blank,0,0);     
    tts_speak( "Test Complete" );
    Thread::wait(500);
    /* Show Calculated alcohol level in PPM and send data via bluetooth to the cloud */
    oled.SetTextProperties(&textProperties);
    oled.Label(ppmText,20,36);
    tts_speak( "you blew" );
    sprintf(text,"%.2f",ppm);
    Thread::wait(50);
    tts_speak( text );
    oled.Label((uint8_t *)text,50,36);
    Thread::wait(50);
    tts_speak( "pee pee emm" );
    Thread::wait(1000);
    
    /* Currently sending to the Pressure variable as a temp place holder */
    kw40z_device.SendiBreathe(ppm); // this is used for custom Hexiware app
    kw40z_device.SendPressure(ppm * 10); // using this to record on Wolksense Cloud
    
    
    /* You've got a Hangover coming!*/
    if ( ppm > 200)
    {
        tts_speak("you have got a hang over coming");
        redLed      = LED_ON;
        greenLed    = LED_OFF;
        blueLed     = LED_OFF;
        
        StartHaptic();
        
        /* Fill 96px by 96px Screen with 96px by 96px Hangover Image starting at x=0,y=0 */
        oled.DrawImage(hang,0,0);
    }   
    
    /* You Shouldn't drive */
    else if (ppm < 200 && ppm > 150)
    {
        tts_speak("you shouldn't drive");
        redLed      = LED_ON;
        greenLed    = LED_OFF;
        blueLed     = LED_ON;
        
        StartHaptic();
      
        /* Fill 96px by 96px Screen with 96px by 96px Don't Drive Image starting at x=0,y=0 */      
        oled.DrawImage(drive,0,0);
    } 
    
    /* You've had a drink */
    else if (ppm < 150 && ppm > 50)
    {
        tts_speak("you have had a drinky");
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_ON;
        
        StartHaptic();
    
        /* Fill 96px by 96px Screen with 96px by 96px Had a drink Image starting at x=0,y=0 */     
        oled.DrawImage(drink,0,0);
    }
    
    /* Sober as a judge*/   
    else
    {
        tts_speak("you are as sober as a judge");
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_OFF;
        
        StartHaptic();
        oled.DrawImage(sober,0,0);    
    }
    Thread::wait(5000);
    
    /* Go back to start screen */
    sysinit();   
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void sysint(void)
* -------------------------------------------------------------------------------------------------
* Overview: System Initial Values Set up
* Input: None
* Output: None
**************************************************************************************************/
void sysinit(void)
{
     /* Set LED to Blue by default*/      
    redLed      = LED_OFF;
    greenLed    = LED_OFF;
    blueLed     = LED_ON;
    
    /* Turn on the backlight of the OLED Display */
    oled.DimScreenON();
    
    /* Fill 96px by 96px Screen with 96px by 96px Welcome Image starting at x=0,y=0 */
    oled.DrawImage(welcome,0,0);
    
    /* Register callbacks to application functions */
    kw40z_device.attach_buttonUp(&ButtonUp);
    kw40z_device.attach_buttonLeft(&ButtonLeft);
    kw40z_device.attach_buttonRight(&ButtonRight);
    kw40z_device.attach_passkey(&PassKey);
    
    /* Send sensor data to bluetooth */
    kw40z_device.SendSetApplicationMode(GUI_CURRENT_APP_SENSOR_TAG);
    
    /* Change font color to White */
    oled.GetTextProperties(&textProperties); 
    textProperties.fontColor   = COLOR_WHITE;
    oled.SetTextProperties(&textProperties);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void voiceinit(void)
* -------------------------------------------------------------------------------------------------
* Overview: initialise voice engine
* Input: None
* Output: None
**************************************************************************************************/
void voiceinit(void)
{
    
    /*setting up the SPI defaults*/
    TextToSpeech.lock();
    TextToSpeech.format(8,3);
    TextToSpeech.frequency(frequency);
    TextToSpeech.unlock();
    
    /* initialise voice */
    pc.printf("System Init Done!\r\n");
    tts_init();
    pc.printf("tts Init Done!\r\n");
    tts_setup();
    pc.printf("tts setup Done!\r\n");
    tts_msg_block_callback( msg_blk );
    tts_fatal_err_callback( fatal_err );
    tts_config( 0x10, false, TTSV_US, 0x0080 );
    tts_unmute();
    
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function msg_blk( uint16_t *req, uint16_t *err )
* -------------------------------------------------------------------------------------------------
* Overview: receive a blocked message from  S1V30120;
**************************************************************************************************/
void msg_blk( uint16_t *req, uint16_t *err )
{
    char txt[ 6 ];
    
    pc.printf( " MSG BLOCKED \r\n" );
    sprintf( txt, "%x\r\n", *req );
    pc.printf( txt );
    sprintf( txt, "%x\r\n", *err );
    pc.printf( txt );
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function fatal_err( uint16_t *err )
* -------------------------------------------------------------------------------------------------
* Overview: error detected
**************************************************************************************************/
void fatal_err( uint16_t *err )
{
    pc.printf( "Fatal Error Detected" );
    tts_init();
    tts_fatal_err_callback( fatal_err );
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* text_to_speech_hal.c
* -------------------------------------------------------------------------------------------------
* Overview: Functions to control SPI interface.
**************************************************************************************************/

void tts_hal_cs_high()
{
    
    TTS_CS = 1;
}

void tts_hal_cs_low()
{
    TTS_CS = 0;
    
}

void tts_hal_mut_high()
{
    TTS_MUTE = 1;
}

void tts_hal_mut_low()
{
    TTS_MUTE = 0;
}

void tts_hal_reset( void )
{
    tts_hal_cs_high();
    TTS_RST = 0;
    tts_hal_write_pad(1);
    wait(0.01);
    TTS_RST = 1;
    wait(POR_TIME);
}

bool tts_hal_msg_rdy( void )
{
    return TTS_RDY;
}

void tts_hal_init()
{
    tts_hal_reset();
    tts_hal_cs_high();
    tts_hal_mut_low();
}

void tts_hal_write( uint8_t *buffer,
                    uint16_t count, bool boot )
{
    TextToSpeech.lock();
    while( count-- )
    {
        if(!boot)
            pc.printf("%02X\r\n", *buffer);
        
        TextToSpeech.write( *buffer++  );
        
     }   
    TextToSpeech.unlock();
}


void tts_hal_write_pad( int cnt )
{
    TextToSpeech.lock();
    tts_hal_cs_low();
    while(cnt--)
    {
         TextToSpeech.write( PADDING_BYTE );
    }  

    tts_hal_cs_high();
    TextToSpeech.unlock();
}

void tts_hal_read( uint8_t *buffer,
                   uint16_t count )
{
    TextToSpeech.lock();
    while(count--)
    {   
        *buffer++ = TextToSpeech.write( DUMMY_BYTE ); //read spi bus
      
       
        //pc.printf("buffer = %X\n\r", *buffer);
    }
    TextToSpeech.unlock();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* text_to_speech.c
* -------------------------------------------------------------------------------------------------
* Overview: Functions to control the setup and speech controls
**************************************************************************************************/

static int _parse_ind( void )
{
    uint16_t rsp_idx = tts_rsp_idx();
    if ( rsp_idx == ISC_MSG_BLOCKED_RESP )
    {
        uint8_t rsp_data[ 4 ] = { 0 };

        _req_err = 0;
        _err_code = 0;

        tts_rsp_data( rsp_data );

        _req_err |= rsp_data[ 0 ];
        _req_err |= rsp_data[ 1 ] << 8;
        _err_code |= rsp_data[ 2 ];
        _err_code |= rsp_data[ 3 ] << 8;

        if( _msg_block_callback != NULL )
            _msg_block_callback( &_req_err, &_err_code );

        return 1;

    } else if ( rsp_idx == ISC_ERROR_IND ) {

         uint8_t rsp_data[ 4 ] = { 0 };

        _req_err = 0;
        _err_code = 0;

        tts_rsp_data( rsp_data );

        _err_code |= rsp_data[ 0 ];
        _err_code |= rsp_data[ 1 ] << 8;

        if ( _err_code && _err_code < 0x8000 )
        {
            if( _err_callback != NULL )
                _err_callback( &_err_code );

        } else if ( _err_code && _err_code > 0x7FFF ) {

            if( _fatal_err_callback != NULL )
                _fatal_err_callback( &_err_code );
        }

        return 1;

    } else if ( rsp_idx == ISC_TTS_READY_IND ) {

        _tts_rdy_f = 1;

    } else if ( rsp_idx == ISC_SPCODEC_READY_IND ) {

        _spc_rdy_f = 1;

    } else if ( rsp_idx == ISC_TTS_FINISHED_IND ) {

        _tts_fin_f = 1;

    } else if ( rsp_idx == ISC_SPCODEC_FINISHED_IND ) {

        _spc_fin_f = 1;
    }

    return 0;
}

void tts_init()
{
    _req_err = 0;
    _err_code = 0;

    _tts_rdy_f = true;
    _spc_rdy_f = true;
    _tts_fin_f = true;
    _spc_fin_f = true;

    _audio_conf.as = 0x00;
    _audio_conf.ag = 0x43;
    _audio_conf.amp = 0x00;
    _audio_conf.asr = ASR_11KHZ;
    _audio_conf.ar = 0x00;
    _audio_conf.atc = 0x00;
    _audio_conf.acs = 0x00;
    _audio_conf.dc = 0x00;

    _tts_conf.sr = 0x01;
    _tts_conf.voice = 0x00;
    _tts_conf.ep = 0x00;
    _tts_conf.lang = TTSV_US;
    _tts_conf.sr_wpm_lsb = 0xc8;
    _tts_conf.sr_wpm_msb = 0x00;
    _tts_conf.ds = 0x00;
    _tts_conf.res = 0x00;

    _pman_conf.am_lsb = 0x01;
    _pman_conf.am_msb = 0x00;
    _pman_conf.spi_clk = 0x01;
    _pman_conf.pad = PADDING_BYTE;

    _flush_enable = false;

    _msg_block_callback = NULL;
    _fatal_err_callback = NULL;
    _err_callback = NULL;

    tts_hw_init();
}

void tts_msg_block_callback( void( *msg_blk_ptr )( uint16_t *req_ptr,
                                                   uint16_t *err_ptr ) )
{
    _msg_block_callback = msg_blk_ptr;
}

void tts_fatal_err_callback( void( *fatal_err_ptr )( uint16_t *err_ptr ) )
{
    _fatal_err_callback = fatal_err_ptr;
}

void tts_err_callback( void( *error_ptr )( uint16_t *err_ptr ) )
{
    _err_callback = error_ptr;
}

void tts_mute()
{
    tts_mute_cmd( true );
}

void tts_unmute()
{
    tts_mute_cmd( false );
}


void tts_setup()
{
    //check HW version
    tts_version_boot(); 
    
    int succ = tts_image_load( (uint8_t*)TTS_INIT_DATA, sizeof( TTS_INIT_DATA ) );
   
    if ( succ != 0x0001 )
    {
            // image load failed, try turning it off and on again
            pc.printf("tts init data failed!\n\r");
            pc.printf("returned value: %X\n\r", succ);
            while(1);
    }        
    pc.printf("tts image load done\n\r"); 
    succ =  tts_image_exec();
    
    if ( succ != 0x0001 )
    {
        // image boot failed, try turning it off and on again
        pc.printf("tts image exec failed!\n\r");
        pc.printf("returned value: %X\n\r", succ);
        while(1);
    } 
    pc.printf("tts image exec done\n\r");
    
    tts_interface_test();
    pc.printf("tts interface test done\n\r");
    
    tts_power_default_config();
    pc.printf("tts power default done\n\r");
    
    tts_audio_default_config();
    pc.printf("tts audio default done\n\r");
    
    tts_volume_set( 0 );
    pc.printf("tts volume set done\n\r");
    
    tts_default_config();
    pc.printf("tts default config done\n\r");
}

void tts_version_boot( void )
{
    
    uint8_t tmp_resp[ 16 ] = { 0 };

    wait( RESET_TO_BOOT_TIME );
   
    tts_parse_req( ISC_VERSION_REQ_BOOT, NULL, 0 );
   
    while( !tts_rsp_chk( ISC_VERSION_RESP_BOOT ) )
    {
        tts_get_resp();
    }
    tts_hal_write_pad( 16);
    tts_rsp_data( tmp_resp );
    pc.printf("hwver0 %X \n\r", tmp_resp[ 0 ]);
    pc.printf("hwver1 %X \n\r", tmp_resp[ 1 ]);
}

uint16_t tts_image_load(const uint8_t *image,
                         uint16_t count )
{
    uint16_t tmp_resp = 0;
    uint16_t index = 0;
    uint8_t raw_resp[ 2 ] = { 0 };
   
    while ( ( count - index ) > ( BOOT_MESSAGE_MAX - 4  ) )
    {
       
       tts_parse_boot_img( image + index, BOOT_MESSAGE_MAX - 4  );
       wait(0.01);
       index += ( BOOT_MESSAGE_MAX - 4 );
    }
    tts_parse_boot_img( image + index, count - index );
    wait(0.01);           
    
    while( !tts_rsp_chk( ISC_BOOT_LOAD_RESP ) )
    {
        tts_get_resp();
          
        if( _parse_ind() )
            return _err_code;
    }
    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;
  
    return tmp_resp;
}

uint16_t tts_image_exec()
{
    uint16_t tmp_resp = 0;
    uint8_t raw_resp[ 2 ] = { 0 };

    tts_parse_req( ISC_BOOT_RUN_REQ, NULL, 0 );
     
    while( !tts_rsp_chk( ISC_BOOT_RUN_RESP ) )
    {
       tts_get_resp();
       
        if( _parse_ind() )
            return _err_code;
    }
    tts_rsp_data( raw_resp );

    tmp_resp |= raw_resp[ 0 ];
    tmp_resp |= raw_resp[ 1 ] << 8;

    return  tmp_resp;
}

uint16_t tts_interface_test()
{
...

This file has been truncated, please download it to see its full contents.

Main.cpp for quick ref

C/C++
Main.cpp for quick reference, for the latest versions always check the repo
/****************************************************************************
* Title                 :   iBreathe Breathalyzer
* Filename              :   breathalyzer
* Author                :   Dave Clarke
* Origin Date           :   27/09/2016
* Notes                 :   Breathalyzer utilizing Hexiware, Alcohol click and Wolksense
*****************************************************************************/
/**************************CHANGE LIST **************************************
*
*    Date    Software Version    Initials       Description
*  27/09/16       1.0.0            DC        Interface Created.
*
*****************************************************************************/

/**
 * @page TEST_CFG Test Configurations
 * <h3> Test configuration : </h3>
 * @par
 * <ul>
 * <li><b> MCU           </b> :      MK64FN1M0XXX12               </li>
 * <li><b> Dev. Board    </b> :      HEXIWEAR                     </li>
 * <li><b> Oscillator    </b> :      12 MHz external              </li>
 * <li><b> Ext. Modules  </b> :      Alcohol Click on mikroBUS 1  </li>
 * <li><b> SW            </b> :      mBed OS5    </li>
 * </ul>
 */

/**
 * @mainpage
 * <h3> Breathalyser created with HEXIWEAR and mBed OS 5 </h3>
 * @par This will show you how much you've drunk and tell you with an Emoticon if
 * you're too hammered to even consider driving. Using the Hexiware app the readings
 * are transmitted to the cloud via bluetooth. Is it time to give up drinking yet?!
 
 * <h3> Alcohol Features </h3>
 * @par Alcohol click, Hexiwear docking station, Hexiware
 */

/******************************************************************************
* Includes
*******************************************************************************/

#include "mbed.h"
#include "Hexi_KW40Z.h"
#include "Hexi_OLED_SSD1351.h"
#include "OLED_types.h"
#include "OpenSans_Font.h"
#include "string.h"
#include "iBreatheImages.h"

/******************************************************************************
* Module Variable Definitions
*******************************************************************************/

#define LED_ON      0
#define LED_OFF     1

                          // Pointers to:  
const uint8_t   *welcome, // Welcome screen image
                *blank,   // blank image
                *blow,    // Start Blowing Image
                *drink,   // You've been drinking image
                *drive,   // Don't drive image
                *hang,    // You'll have a hangover image
                *ini,     // Initialising image
                *sober;   // Sober as a judge image

const float     Vadc_3V3  = 0.00005035;    // 16-Bit ADC step 3V3/65535   = 0.05035 mV
       
float           Vrl = 0,                   // Output voltage
                ambientAlc = 0,            // Abmient Output voltage from sensor
                SensorRes = 0,             // SensorRes (Ohm) - Sensor resistance
                SensorRes1 = 0,            // SensorRes (Ohm) - Sensor resistance
                ppm = 0,                   // ppm
                ppm_1 = 0,                 // Ambient ppm variable  
                ratio = 0;                 // SensorRes/LoadRes ratio
              
unsigned short  adc_rd = 0; //Initialise anologue read variable

const uint8_t   ppmText[] = "ppm:"; // text for PPM label

char            text[20],           // Text array variables
                text2[20],
                text3[20];

float           value[20],  // initial sensor set up values
                value1[20]; // initial sensor set up values

bool            isFirstBoot = true;                   
                

/******************************************************************************
* Function Prototypes
*******************************************************************************/

void sysinit(void);
void ReadSensor();
void CalculatePPM(int times, bool amb);
void ambient(int times);
void StartHaptic(void);
void StopHaptic(void const *n);
void ButtonUp(void);
void txTask(void);

/******************************************************************************
* Instance setups
*******************************************************************************/
/* Define timer for haptic feedback */
RtosTimer hapticTimer(StopHaptic, osTimerOnce);

//set up Analog read pin for alcohol sensor
AnalogIn Alcohol(PTB2);

/* Instantiate the SSD1351 OLED Driver */ 
SSD1351 oled(PTB22,PTB21,PTC13,PTB20,PTE6, PTD15); /* (MOSI,SCLK,POWER,CS,RST,DC) */
      
/* Get OLED Class Default Text Properties */
oled_text_properties_t textProperties = {0};

/* Instantiate the Hexi KW40Z Driver (UART TX, UART RX) */ 
KW40Z kw40z_device(PTE24, PTE25);

/* LED and Haptic Set ups */
DigitalOut redLed(LED1);
DigitalOut greenLed(LED2);
DigitalOut blueLed(LED3);
DigitalOut haptic(PTB9);

/******************************************************************************
* Bluetooth button functions and passkey function
*******************************************************************************/

void ButtonRight(void)
{
    StartHaptic();
    kw40z_device.ToggleAdvertisementMode();
    blueLed = kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    redLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    greenLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
}

void ButtonLeft(void)
{
    StartHaptic();
    kw40z_device.ToggleAdvertisementMode();
    blueLed = kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    redLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
    greenLed = !kw40z_device.GetAdvertisementMode(); /*Indicate BLE Advertisment Mode*/
}

void PassKey(void)
{
    StartHaptic();
    strcpy((char *) text,"PAIR CODE");
    oled.TextBox((uint8_t *)text,0,25,95,18);
  
    /* Display Bond Pass Key in a 95px by 18px textbox at x=0,y=40 */
    sprintf(text3,"%d", kw40z_device.GetPassKey());
    oled.TextBox((uint8_t *)text3,0,40,95,18);
}

/******************************************************************************
* Main
*******************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main()
{
     /* Set pointers to the BMPs stored in  iBreatheImages.c */
     welcome = iBreatheWS_bmp;    // Welcome screen image 
     blank = iBreatheBlank_bmp;   // blank image
     blow = iBreatheBlow_bmp;    // Start Blowing Image
     drink = iBreatheDrink_bmp;   // You've been drinking image
     drive = iBreatheDrive_bmp;   // Don't drive image
     hang = iBreatheHang_bmp;    // You'll have a hangover image
     ini = iBreatheini_bmp;     // Initialising image
     sober = iBreatheSober_bmp;   // Sober as a judge image
       
     /* Set initial Values */
     sysinit();
}

/******************************************************************************
* Public Function Definitions
*******************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function readSensor(void)
* -------------------------------------------------------------------------------------------------
* Overview: Read sensor
* Input: None
* Output: None
**************************************************************************************************/

void ReadSensor()
{
    /* Read 16 Bit Analog value */
    adc_rd = Alcohol.read_u16();

    // pause 200ms 
    Thread::wait(200);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function CalculatePPM(Int times, bool amb)
* -------------------------------------------------------------------------------------------------
* Overview: Calculation of PPM
* Input: times = Number of samples to take, amb sets either ambient reading or test reading (true for test)
* Output: None
**************************************************************************************************/

void CalculatePPM(int times, bool amb)
{
        float   lgPPM;
        
        /* Read values x times */
        for(int x = 0; x < times; x++)
        {
            ReadSensor();    
            value[x] = ((float)adc_rd  * Vadc_3V3);
            StartHaptic();
            Thread::wait(50);
        }    
        
        /* Calculate the average value for accuratcy */
        for(int y = 0; y < times; y++)
        {
            Vrl += value[y];
        }
        Vrl = Vrl / times;       

        /* Set SensorRes reference value */
        SensorRes =    (Vrl / 3.3);
        
        /* Set ratio */
        ratio     = SensorRes1 / SensorRes;

        /* Convert to PPM */
        lgPPM = ( log10( ratio ) * -1.5512 ) + 2.5911;
        
        /* If true create test result, flase creates reference result */
        if (amb == true)
        {
            ppm = pow( 10, lgPPM );
        }
        else
        {
            ppm_1 = pow( 10, lgPPM );
        }    
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void ambient(int times)
* -------------------------------------------------------------------------------------------------
* Overview: Reading of the Ambient Voltgage of the sensor for reference
* Input: times = Number of samples to take
* Output: None
**************************************************************************************************/

void ambient(int times)
{
    /* Read ambient values x times flashing green led*/
    for(int x = 0; x < times; x++)
    {
        redLed      = LED_OFF;
        greenLed    = LED_OFF;
        blueLed     = LED_OFF;
        
        ReadSensor();
        value1[x] = (float)adc_rd  * Vadc_3V3; 
        
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_OFF;
        Thread::wait(48);   
    }
    
    /* Calculate the average value for accuratcy */
    for(int y = 0; y < times; y++)
    {
        ambientAlc+=value1[y];            
    }
    ambientAlc = ambientAlc / times;
  
    /* Set SensorRes1 reference value */
    SensorRes1 =   (ambientAlc / 3.3);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void StartHaptic(void)
* -------------------------------------------------------------------------------------------------
* Overview: Start Buzzing haptic motor
* Input:  None
* Output: None
**************************************************************************************************/

void StartHaptic(void)
{
    hapticTimer.start(50);
    haptic = 1;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void StopHaptic(void)
* -------------------------------------------------------------------------------------------------
* Overview: Stop Buzzing haptic motor
* Input:  None
* Output: None
**************************************************************************************************/

void StopHaptic(void const *n) {
    haptic = 0;
    hapticTimer.stop();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void ButtonUp(void)
* -------------------------------------------------------------------------------------------------
* Overview: Function called whe up button pressed, Begins Breathilyzer testing procedure
* Input:  None
* Output: None
**************************************************************************************************/

void ButtonUp(void)
{
    StartHaptic();
    
    bool Ref = false;
    bool Test = true;
        
    /* LED set to green for test beginning*/
    redLed      = LED_OFF;
    greenLed    = LED_ON;
    blueLed     = LED_OFF;
    
    /* Fill 96px by 96px Screen with 96px by 96px Initialising Image starting at x=0,y=0 */   
    oled.DrawImage(ini,0,0);
    
    /* first boot bug work around to stop junk values */
    if (isFirstBoot == true)
    {
        /*read ambient atmosphere levels with 10 samples and set flag to show it's ambient basline figure*/
        ambient(1);
        CalculatePPM(1,Ref);
        isFirstBoot = false;
    }
    
    /*read ambient atmosphere levels with 10 samples and set flag to show it's ambient basline figure*/
    ambient(10);
    CalculatePPM(10,Ref);
    
    
    /* Fill 96px by 96px Screen with 96px by 96px Blowing Image starting at x=0,y=0 */  
    oled.DrawImage(blow,0,0);
    
    /*read breathe alcohol levels with 10 samples and set flag to show it's breathilyzer test figure*/
    CalculatePPM(10,Test);
    
    /*Calculate the difference in Alcohol level based on Ambient and test sample*/
    ppm = ppm - ppm_1;
    
    /*Throw away any values less than 0*/
    if (ppm < 0)
    {
         ppm = 0;
    }     
    
    /* Fill 96px by 96px Screen with 96px by 96px Blank Background Image starting at x=0,y=0 */
    oled.DrawImage(blank,0,0);     
    
    /* Show Calculated alcohol level in PPM and send data via bluetooth to the cloud */
    oled.SetTextProperties(&textProperties);
    oled.Label(ppmText,20,36);
    sprintf(text,"%.2f",ppm);
    oled.Label((uint8_t *)text,50,36);
    Thread::wait(1000);
    
    /* Currently sending to the Pressure variable as a temp place holder */
    kw40z_device.SendiBreathe(ppm); // this is used for custom Hexiware app
    kw40z_device.SendPressure(ppm * 10); // using this to record on Wolksense Cloud
    
    
    /* You've got a Hangover coming!*/
    if ( ppm > 200)
    {
        redLed      = LED_ON;
        greenLed    = LED_OFF;
        blueLed     = LED_OFF;
        
        StartHaptic();
        
        /* Fill 96px by 96px Screen with 96px by 96px Hangover Image starting at x=0,y=0 */
        oled.DrawImage(hang,0,0);
    }   
    
    /* You Shouldn't drive */
    else if (ppm < 200 && ppm > 150)
    {
        redLed      = LED_ON;
        greenLed    = LED_OFF;
        blueLed     = LED_ON;
        
        StartHaptic();
      
        /* Fill 96px by 96px Screen with 96px by 96px Don't Drive Image starting at x=0,y=0 */      
        oled.DrawImage(drive,0,0);
    } 
    
    /* You've had a drink */
    else if (ppm < 150 && ppm > 50)
    {
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_ON;
        
        StartHaptic();
    
        /* Fill 96px by 96px Screen with 96px by 96px Had a drink Image starting at x=0,y=0 */     
        oled.DrawImage(drink,0,0);
    }
    
    /* Sober as a judge*/   
    else
    {
        redLed      = LED_OFF;
        greenLed    = LED_ON;
        blueLed     = LED_OFF;
        
        StartHaptic();
        oled.DrawImage(sober,0,0);    
    }
    Thread::wait(5000);
    
    /* Go back to start screen */
    sysinit();   
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**************************************************************************************************
* Function void sysint(void)
* -------------------------------------------------------------------------------------------------
* Overview: System Initial Values Set up
* Input: None
* Output: None
**************************************************************************************************/
void sysinit(void)
{
    /* Set LED to Blue by default*/      
    redLed      = LED_OFF;
    greenLed    = LED_OFF;
    blueLed     = LED_ON;
    
    /* Turn on the backlight of the OLED Display */
    oled.DimScreenON();
    
    /* Fill 96px by 96px Screen with 96px by 96px Welcome Image starting at x=0,y=0 */
    oled.DrawImage(welcome,0,0);
    
    /* Register callbacks to application functions */
    kw40z_device.attach_buttonUp(&ButtonUp);
    kw40z_device.attach_buttonLeft(&ButtonLeft);
    kw40z_device.attach_buttonRight(&ButtonRight);
    kw40z_device.attach_passkey(&PassKey);
    
    /* Send sensor data to bluetooth */
    kw40z_device.SendSetApplicationMode(GUI_CURRENT_APP_SENSOR_TAG);
    
    /* Change font color to White */
    oled.GetTextProperties(&textProperties); 
    textProperties.fontColor   = COLOR_WHITE;
    oled.SetTextProperties(&textProperties);
}

Talking iBreathe Breathalyzer

Using the Text To Speech click board to add a voice to the iBreathalyzer

iBreathe Breathalyzer

This is a copy from my mBed repo

iBreathe Breathalyzer Hexiware iOS App

Modified Hexiware app code to work with iBreathe Breathalyzer

Credits

Dave Clarke

Dave Clarke

14 projects • 83 followers
Industrial Designer, Maker of Things, Lover of Phi and all around nice guy

Comments