target icon

Purpose

This tutorial shows a very simple DIY prototype of a meteo station including a temperature sensor, an ambiant light sensor and a screen.

list icon

Prerequisites
  • The Pyboard and the training daughter board from HyperPanel Lab.
  • An installation of a HyperpanelOS release already installed on a Linux PC, including tools for flashing and debugging. You need at least two USB ports on the computer (one for flashing and running, one for terminal).
  • The Olimex ARM-USB-OCD interface & cables.
  • A Grove-LCD RGB Backlight V4.0 device.
  • A HALJIA PCF8591 8-bit AD & DA convertor.

list icon

Software release
hypv9wr64 or higher.

align left icon

Description

This app is a simple DIY example of using the Pyboard / Hyperpanel daughter board to make a meteo station. Two different I2C devices are use: an integrated multi sensors from HALJIA and a screen.

HALJIA sensor :

HALJIA PCF8591

This is an integrated package with 2 analog sensors (temperature and ambiant light), an analog potentiometer and an A/D converter (NXP PCF8591). The A/D microship is an 4 channels analog to digital converter (ADC). It’s also an one channel D/A converter (DAC) but the DAC feature is not used in this app. This package is already pre-assembled and configured as followed:

  • A thermistor is connected to analog input 0 (for temperature)
  • A potentiometer is connected to analog input 1 (for general purpose)
  • A photoresistor is connected to analog input 2 (for ambient light)

Screen :

The screen is the Grove-LDC RGB Backlight V4.0 from www.seeedstudio.com. This is the same screen than explained in the tutorial #217 (Screen LCD & RGB). The Grove-LCD RGB Backlight includes 2 different I2C devices: One is the LCD display, the other one is the RGB colored background.

Photoresistor linear conversion :

The app is a very simple conversion to get the current Lux value from the analog value read from the ADC convertor. We assume than in total dark the lux value is “O Lux” and in standard environment (according to Wiki) the analog value read from ADC register (value is 80) correspond to almost 300 Lux.

  • Analog read value : 80 -> 300 Lux
  • Analod read value : 200 -> 0 Lux

With these two points and a linearity approximation, it results this conversion :

lux value = (-30/12)(analog value) + 500

 

  • The app is a main loop based on a timer. At each timer time-out (200ms), we read the three currents values of the ADC, converts read values into consistent values, and display these values on the screen. Each 2sec, we also send messages to serial port.
  • The analog potentiometer is also connected to the ADC. This value is used to trigg the contrast of the screen, from “totally dark” to “max brightness value”.

Temperature and ambient light detection.

Screen contrast triggering.

Code

hypos-tuto-225.c

/*
**  hypos-tuto-225.c - Sample code for HyperpanelOS =======================  **
**                                                                           **
**  This simple code is located into the application container, it is run    **
**  by the VMK sub-operating system. On the other hand, the I/O container    **
**  runs all the drivers that are VMIO finite state machines.                **
**                                                                           **
**  The goal of this app is to use a PCF8591 8-bit A/D and D/A converter     **
**                                                                           **
**  =======================================================================  **
*/


/* Documentation ...................-----------------------------------------

   This app is a simple DIY example of using the Pyboard / Hyperpanel daughter
   board to make a meteo station. Two different I2C devices are use : a
   integrated multi sensor from HALJIA and a screen. This app corresponds to to
   tutorial #225 available on tutorial.hyperpanel.com

   - HALJIA sensor:

   www.amazon.fr/HALJIA-PCF8591-AD-Conversion-Analogique-analogique/dp/B0112SH1TG

   This is a integrated package with 2 analog sensors (temparature and ambiant
   light), an analog potentiometer and a A/D converter (NXP PCF8591). The
   A/D microship is an 4 channels analog to digital converter (ADC). It's also a
   one chanel D/A converter (DAC) but the DAC feature is not used in this app.
   This package is already pre-assembled and configured as followed:

   A thermistor is connected to analog input 0 (for temperature)
   A potentiometer is connected to analog input 1 (for general purpose)
   A photoresitor is connected to analog input 2 (for ambiant light)

   the analog input 3 is unused. The A/D converter is the NXP PCF8591 8-bit
   A/D and D/A converter from NXP. The datasheet is available in this tutorial
   (datasheet-adc-PCF8591.pdf).

   - Screen:

   The screen is the Grove-LDC RGB Backlight V4.0 from www.seedstudio.com. This
   is the same screen than explained in the tutorial #217 (Screen LCD & RGB).

   - Photoresistor linear conversion :

   The app is a very simple conversion to get the current Lux value from the
   analog value read from the ADC convertor. We assume than in total dark the
   lux value is "O Lux" and in standard (according to Wiki) the analog value
   read from ADC register (value is 80) correspond to almost 300 Lux.

   Analog read value :  80 -> 300 Lux
   Analod read value : 200 ->   0 Lux

   With these two points and a linearity approximation, it results this
   conversion :

   lux value = (-30/12)(analog value) + 500

   - The app is a main loop based on a timer. At each timer time-out (200ms),
   we read the three currents values of the ADC, converts read values into
   consistent values, and send these values on the screen. Each 2sec, we also
   send messages to serial port.

   - The analog potentiometer is also connected to the ADC. This value is used
   to trig the contrast of the screen, from "totally dark" to "max brightness
   value".


*/
/* Include files and external reference -------------------------------------*/

#include <hypos.h>                     // Hyperpanel OS basic interfaces.    */
#include <drv_asy.h>                   // Prototype of "asy_write()".        */
#include <drv_i2c.h>                   // Prototype of "i2c_*()".            */


/* Internal defines of this module ------------------------------------------*/

#define  TICK                 1000     // Code for tick event.

#define DUMMY_AD              0xFF     // I2C dev address - Dummy for wait.
#define LCD_AD                0x7c     // I2C dev address - LCD (screen)
#define RGB_AD                0xc4     // I2C dev address - RGB (screen)
#define ADC_AD_W              0x90     // I2C dev address - ADC (write)
#define ADC_AD_R              0x91     // I2C dev address - ADC (read)

#define RED                   0x04     // I2C register - RGB background color.
#define GREEN                 0x03     // I2C register - RGB background color.
#define BLUE                  0x02     // I2C register - RGB background color.

#define SCREEN_INIT              0     // I2C command - Screen initialisation
#define SET_LINE1                1     // I2C command - Cursor on line 1
#define SET_LINE2                2     // I2C command - Cursor on line 2
#define ADC_INIT                 3     // I2C command - ADC/DAC initialisation


/*  Internal global variables of this module --------------------------------*/

    static int      idto             ; // Timer identifier.


/* SCREEN_INIT command ......................................................*/

    static const char screen_init[]  = // I2C command - Screen initialisation
      {

/*    Length-1, Address, Control byte, Data byte                             */

      1, DUMMY_AD ,   50             , // I2C temporisation 50 ms

      2, LCD_AD   , 0x80 , 0x3C      , // LCD dev  : FUNCTION_SET try 1
                                       // Ctrl byte: 10000000 RS=0
                                       // Data byte: 00111100 2-line, on

      1, DUMMY_AD ,   40             , // I2C temporisation 40 ms

      2, LCD_AD   , 0x80 , 0x3C      , // LCD dev  : FUNCTION_SET try 2
                                       // Ctrl byte: 10000000 RS=0
                                       // Data byte: 00111100 2-line, on

      1, DUMMY_AD ,   40             , // I2C temporisation 40 ms

      2, LCD_AD   , 0x80 , 0x3C      , // LCD dev  : FUNCTION_SET try 3
                                       // Ctrl byte: 10000000 RS=0
                                       // Data byte: 00111100 2-line, on

      2, LCD_AD   , 0x80 , 0x0C      , // LCD dev  : DISPLAY_SWITCH
                                       // Ctrl byte: 10000000 RS=0
                                       // Data byte: 00001100 on,off,off

      1, DUMMY_AD ,    40            , // I2C temporisation more than 39us

      2, LCD_AD   , 0x80 , 0x01      , // LCD dev  : SCREEN_CLEAR
                                       // Ctrl byte: 10000000 RS=0
                                       // Data byte: 00000001 clear

      1, DUMMY_AD ,    2             , // I2C temporisation more than 1.53ms

      2, RGB_AD   , 0x00 , 0x00      , // RGB dev  : REG_MODE1, init
                                       // Register : 0x00
                                       // Data     : 0x00

      2, RGB_AD   , 0x08 , 0xFF      , // RGB dev  : REG_OUTPUT
                                       // Register : 0x08
                                       // Data     : 0xFF PWM & GRPPWM

      0, 0        , 0    , 0         , // End of command set
      }                              ; //

/* SET_LINE1 command ........................................................*/

    static const char set_line1[]    = // I2C command - Screen initialisation
      {

/*    Length-1, Address, Control byte, Data byte                             */

      2, LCD_AD   , 0x80 , 0x80      , // LCD dev  : CURSOR_RETURN
                                       // Ctrl byte: 10000000 RS=0
                                       // Data byte: 10000000 line 1, on

      0, 0        ,    0 ,    0      , // End of commands
      }                              ; //

/* SET_LINE2 command ........................................................*/

    static const char set_line2[]    = // I2C command - Screen initialisation
      {

/*    Length-1, Address, Control byte, Data byte                             */

      2, LCD_AD   , 0x80 , 0xC0      , // LCD dev  : CURSOR_RETURN
                                       // Ctrl byte: 10000000 RS=0
                                       // Data byte: 11000000 line 2, on

      0, 0        ,    0 ,    0      , // End of commands
      }                              ; //

    static const char adc_init[]     = // I2C command - ADC/CAD initialisation
      {

/*    Length-1, Address, Control byte, Data byte                             */

      1, ADC_AD_W , 0x00             , // Write the configuration register
                                       // Cf. datasheet page 19)
      0, 0        , 0    , 0         , // End of command set
      }                              ; //

    static char   *command[] =         // I2C commands table
      {                                //
        (char*)&screen_init[0]       , // I2C command - SCREEN_INIT
        (char*)&set_line1[0]         , // I2C command - SET_LINE1
        (char*)&set_line2[0]         , // I2C command - SET_LINE2
        (char*)&adc_init[0]          , // I2C command - ADC_INIT  
        (char*)0
      }                              ; //


/*  Prototypes --------------------------------------------------------------*/

    static int  loop_app_task(void*) ; // Prototype
    static int  wait_evt(void)       ; // Prototype
    static void set_command(int)     ; // Prototype
    static void write_string(char*)  ; // Prototype
    static void set_color(int,int)   ; // Prototype


/*  Beginning of the code ---------------------------------------------------

    loop_app_tsk         Application entry point
    wait_evt             Wait for applicative events
    set_command          Write a command on the screen
    write_string         Write a character string on the screen
    set_color            Set a color level on the screen background
*/

/*  Procedure loop_app_tsk --------------------------------------------------

    Purpose : This is our task main loop.
*/

int loop_app_tsk (void *param)
  {
    int             ev = TICK        ; // Our event
    int             curtime = 0      ; // Current time          
    char            mess[64]         ; // Message to be sent on ASY0
    int             tval,temp        ; // Current temperature
    int             lval, light      ; // Current ambiant light
    int             pval,pot         ; // Current potentiometer value
    unsigned char   tframe[2]        ; // I2C read frame
    unsigned char   lframe[2]        ; // I2C read frame
    unsigned char   pframe[2]        ; // I2C read frame
    int             ret              ;


/* Start a timer that will send an event every 200 ms .......................*/

    set_tto(CLOCK      ,               // Timer mode: clock
            200        ,               // Duration in milliseconds
            TICK , 0   ,               // Event code and reserve field
            &idto     );               // Timer identifier

/* Initalization of the two I2C devices (Screen and ADC) ....................*/

    asy_write(0,(unsigned char*)"Init ...\r\n",10);

    set_command(SCREEN_INIT)         ; // LCD/RGB initialization sequence
    set_command(ADC_INIT)            ; // ADC     initialization sequence

    asy_write(0,(unsigned char*)"done\r\n",6);

/* Main loop ................................................................*/

    tval=temp=lval=light=pval=pot=0  ; // Output values initialisation.

    wait_ev :                          // Beginning of loop label
 
    suspend_task(-1,&ret);

    if (ev == TICK)                    // If the event is the tick event
      {                                //

        curtime ++                   ; // Time incrementation

/* Read analog values and make some simple conversions ......................*/

        tframe[0] = 0                ; // Get the temperature.
        i2c_read(0,ADC_AD_R,
                       tframe,2,0x00);
        tval = (int)tframe[0]        ;
        temp =  144 - tval           ; // Linear approximation.

        pframe[0] = 0                ; // Get the potentiometer value.
        i2c_read(0,ADC_AD_R,
                       pframe,2,0x01);
        pval = (int)pframe[0]        ;
        pot = pval/2                 ;

        lframe[0] = 0                ; // Get the ambiant light.
        i2c_read(0,ADC_AD_R,          
                       lframe,2,0x02);
        lval = (int)lframe[0]        ;
        light = Max(0,                 // Linear approximation.
                  (500-(30*lval)/12)); // analog value= 80 -> Light=300 Lux
                                       // analog value=200 -> Light=  0 Lux

/* Display information of the screen ........................................*/

        hsprintf(mess                , // Format de timer message.
          "Temperature %2d C",temp  ); //
        set_command(SET_LINE1)       ; // Cursor on line 1.
        write_string(mess)           ; // Write current time on screen.

        hsprintf(mess                , // Format de timer message.
          "Light  %5d Lux",light   ); //
        set_command(SET_LINE2)       ; // Cursor on line 1.
        write_string(mess)           ; // Write current time on screen.

/* Update screen contrast ...................................................*/

        set_color(RED  , pot)        ; // Set RED level
        set_color(GREEN, pot)        ; // Set GREEN level
        set_color(BLUE , pot)        ; // Set BLUE level

/* Send information to serial port (message console) ........................*/

        if (!(curtime%10))             // Each second, send a trace.
          {                            //
            hsprintf(mess            ,
              "Thermistor    - analog read: %3d temperature: %d C\r\n",
               tframe[0],temp            );
            asy_write(0              , // Write on ASY0
              (unsigned char*)mess   , // the "mess" message
              strlen(mess)          ); // Count of bytes to be sent

            hsprintf(mess            ,
              "Photoresistor - analog read: %3d light      : %d Lux\r\n",
               lframe[0],light           );
            asy_write(0              , // Write on ASY0
              (unsigned char*)mess   , // the "mess" message
              strlen(mess)          ); // Count of bytes to be sent

            hsprintf(mess            ,
              "Potentiometer - analog read: %3d contrast   : %d\r\n\r\n",
               pframe[0],pot             );
            asy_write(0              , // Write on ASY0
              (unsigned char*)mess   , // the "mess" message
              strlen(mess)          ); // Count of bytes to be sent
          }
      }

    ev = wait_evt()                  ; // Unschedule until an event is received
    goto wait_ev                     ; // Wait for the next event

    return  0                        ; // Return code of the procedure 
  }

/*  Procedure wait_evt ------------------------------------------------------*/
/*
    Purpose : Unschedule until the next event is received, whatever it is.
*/

static int wait_evt (void)
  {
    int             waitlist[1][3]   ; // Parameter of "waitevt_task"
    int             ret              ; // Return code for "waiyevt_task"

/*****************************************************************************
 * Step 1 : Build a list with one WAIT_CODEINT entry that will accept all    *
 * ------   the event codes ranging from 0 to 20000. Then call               *
 *          "waitevt_task", we will be unscheduled until the next event will *
 *          be received                                                      *
 *****************************************************************************/

    waitlist[0][0]  = WAIT_CODEINT   ; // All events with
    waitlist[0][1]  = 0              ; // a code between 0
    waitlist[0][2]  = 20000          ; // and 20000

    waitevt_task(waitlist ,            // Address of waiting list
                 1        ,            // Size of "waitlist[]"
                 0        ,            // maximum waiting time = no
                 0        ,            // Do not purge previous events
                 &ret       )        ; // Return code

/*****************************************************************************
 * Step 2 : Here we are scheduled again. The VMK has written into its        *
 * ------   global variable "task_evt" a copy of the event that has          *
 *          scheduled us again.                                              *
 *****************************************************************************/

    return task_evt.code             ; // Return event code
  }

/*  Procedure set_command ---------------------------------------------------*/
/*
    Purpose : Send a set of commands to I2C devices.
*/

static void set_command(int cmd)
  {
    i2c_write(
      0                           ,    // Controller number
     -1                           ,    // Target I2C write address
      (unsigned char*)command[cmd],    // Command
      0                          );    // List of option flags
  }

/*  Procedure write_string --------------------------------------------------*/
/*
    Purpose : write a character string on the screen.
*/

static void write_string(char *s)
  {
    int             of = 0           ; // Offset for I2C frame building
    unsigned char   buf[32]          ; // Message to be sent on I2C

    memset(buf  ,strlen(s)+1,1);of++ ; // Lenght -1 ot the message
    memset(buf+of, LCD_AD   ,1);of++ ; // write address 01111100
    memset(buf+of, 0x40     ,1);of++ ; // Control Byte 01000000 (RS=1)
    memcpy(buf+of,s,strlen(s) )      ; //
    of=of+strlen(s)                  ; //
    memset(buf+of,0,1)               ; // End of frame

    i2c_write(0,-1,buf,0)            ; // Send I2C commands.
  }

/*  Procedure set_color -----------------------------------------------------*/
/*
    Purpose : Set a color level on RGB background
*/

static void set_color(int color, int level)
  {
    int             of = 0           ; // Offset for I2C frame building
    unsigned char   buf[8]           ; // Message to be sent on I2C

    memset(buf   ,    2  , 1); of++  ; // Lenght -1 ot the message
    memset(buf+of, RGB_AD, 1); of++  ; // write address
    memset(buf+of, color , 1); of++  ; // REG_RED  
    memset(buf+of, level , 1); of++  ; // 
    memset(buf+of, 0,      1)        ; // End of frame

    i2c_write(0,-1,buf,0)            ; // Send I2C commands.
  }