Saturday, 27 July 2013

I2C Communication - Interfacing Sensors - MPU6050, BMP085 & HMC5883L

There are a number of sensors with I2C interface. The I2Cdev project offers a set of libraries to work with these.  We will look at how to interface 3 such sensors(one at a time!) to our Induino R3 Board (same works for all Arudino Boards as well!) using this library.

Download the I2Cdevlib from here https://github.com/jrowberg/i2cdevlib/archive/master.zip
Extract the downloaded file and go inside the Arduino folder inside the extracted file.  This folder has a number of libraries. We will copy the 4 libraries - I2Cdev, MPU6050, BMP085 & HMC5883L. Follow the manual library installation process from here for each of these libraries.

To interface these sensors, you would need a breadboard and some wires to place and connect these sensors.

MPU6050 - 6 DOF - 3-Axis Accelerometer + 3-Axis Gyro

The MPU6050 is a motion tracking device from Invensense.  The sensor combines a 3-axis gyroscope and a 3-axis accelerometer on the same silicon die together with an onboard Digital Motion Processor™ (DMP™) capable of processing complex MotionFusion algorithms. While all this might be high tech only a limited amount of that technology is available outside the official software required to work with it. Luckily,  the I2Cdev library exposes most of the functions.  You can buy it here -> MPU 6050 Breakout Board

Download the Product Specification Sheet Here
Download the Product Register Map Here


Read this to know more about Accelerometers & Gyroscopes ->
http://www.instructables.com/id/Accelerometer-Gyro-Tutorial/

Connecting the MPU6050  ( connections are for the module above with model number GY-521 for other modules refer to the module datasheet and connect accordingly)

  • Place the MPU6050  Breakout Board on the breadboard
  • Connect the SDA pin on the MPU6050 to the SDA pin on the Induino R3
  • Connect the SCL pin on the MPU6050 to the SCL pin on the Induino R3
  • Conect the Vcc pin on the MPU6050 to 5Volts on the Induino R3
  • Connect the GND pin on the MPU6050 to the GND pin on the Induino R3
  • Connect the INT pin on the MPU6050 to digital pin 2 on the Induino R3 (this is an interrupt pin - more about this later)

Go to File->Examples->MPU6050-> and click on MPU6050_DMP6

Upload the program and watch the various values for Accelerometer / Gyro in your serial monitor. Now this program has options to configure various calculated outputs. Go through the comments and use wisely.


BMP085 - Barometric Pressure Sensor

The BMP085 is a high-precision, ultra-low power barometric pressure sensor. It can be used for a number of applications. Barometric Pressure can be used to calculate a number of factors like altitude, etc. You can buy it here -> BMP085 Breakout Board

Download the Datasheet Here
Connecting the BMP085  ( connections are for the module above with model number GY-65 for other modules refer to the module datasheet and connect accordingly)
  • Place the BMP085 Breakout Board on the breadboard
  • Connect the SDA pin on the BMP085 to the SDA pin on the Induino R3
  • Connect the SCL pin on the BMP085 to the SCL pin on the Induino R3
  • Conect the Vcc pin on the BMP085 to 5Volts on the Induino R3
  • Connect the GND pin on the BMP085 to the GND pin on the Induino R3
Go to File->Examples->BMP085-> and click on BMP085_basic

Upload the program and watch the various values for pressure in your serial monitor. These are raw values. You can further experiment by modifying the following program from http://bildr.org/ This Program works on bare I2C communication without any libraries.

/*  Induino R3 User Guide - Program 12.0 - Interfacing the GY-65 BMP085 Breakout Board with the Induino R3*/
/* Code from Bildr.org */

/*Based largely on code by  Jim Lindblom
 
 Get pressure, altitude, and temperature from the BMP085.
 Serial.print it out at 9600 baud to serial monitor.
 */

#include <Wire.h>

#define BMP085_ADDRESS 0x77  // I2C address of BMP085

const unsigned char OSS = 0;  // Oversampling Setting

// Calibration values
int ac1;
int ac2;
int ac3;
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1;
int b2;
int mb;
int mc;
int md;

// b5 is calculated in bmp085GetTemperature(...), this variable is also used in bmp085GetPressure(...)
// so ...Temperature(...) must be called before ...Pressure(...).
long b5; 

void setup(){
  Serial.begin(9600);
  Wire.begin();
  bmp085Calibration();
}

void loop()
{
  float temperature = bmp085GetTemperature(bmp085ReadUT()); //MUST be called first
  float pressure = bmp085GetPressure(bmp085ReadUP());
  float atm = pressure / 101325; // "standard atmosphere"
  float altitude = calcAltitude(pressure); //Uncompensated caculation - in Meters 

  Serial.print("Temperature: ");
  Serial.print(temperature, 2); //display 2 decimal places
  Serial.println("deg C");

  Serial.print("Pressure: ");
  Serial.print(pressure, 0); //whole number only.
  Serial.println(" Pa");

  Serial.print("Standard Atmosphere: ");
  Serial.println(atm, 4); //display 4 decimal places

  Serial.print("Altitude: ");
  Serial.print(altitude, 2); //display 2 decimal places
  Serial.println(" M");

  Serial.println();//line break

  delay(1000); //wait a second and get values again.
}

// Stores all of the bmp085's calibration values into global variables
// Calibration values are required to calculate temp and pressure
// This function should be called at the beginning of the program
void bmp085Calibration()
{
  ac1 = bmp085ReadInt(0xAA);
  ac2 = bmp085ReadInt(0xAC);
  ac3 = bmp085ReadInt(0xAE);
  ac4 = bmp085ReadInt(0xB0);
  ac5 = bmp085ReadInt(0xB2);
  ac6 = bmp085ReadInt(0xB4);
  b1 = bmp085ReadInt(0xB6);
  b2 = bmp085ReadInt(0xB8);
  mb = bmp085ReadInt(0xBA);
  mc = bmp085ReadInt(0xBC);
  md = bmp085ReadInt(0xBE);
}

// Calculate temperature in deg C
float bmp085GetTemperature(unsigned int ut){
  long x1, x2;

  x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  x2 = ((long)mc << 11)/(x1 + md);
  b5 = x1 + x2;

  float temp = ((b5 + 8)>>4);
  temp = temp /10;

  return temp;
}

// Calculate pressure given up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
long bmp085GetPressure(unsigned long up){
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;

  b6 = b5 - 4000;
  // Calculate B3
  x1 = (b2 * (b6 * b6)>>12)>>11;
  x2 = (ac2 * b6)>>11;
  x3 = x1 + x2;
  b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2;

  // Calculate B4
  x1 = (ac3 * b6)>>13;
  x2 = (b1 * ((b6 * b6)>>12))>>16;
  x3 = ((x1 + x2) + 2)>>2;
  b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;

  b7 = ((unsigned long)(up - b3) * (50000>>OSS));
  if (b7 < 0x80000000)
    p = (b7<<1)/b4;
  else
    p = (b7/b4)<<1;

  x1 = (p>>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;

  long temp = p;
  return temp;
}

// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address)
{
  unsigned char data;

  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();

  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available())
    ;

  return Wire.read();
}

// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address)
{
  unsigned char msb, lsb;

  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();

  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2)
    ;
  msb = Wire.read();
  lsb = Wire.read();

  return (int) msb<<8 | lsb;
}

// Read the uncompensated temperature value
unsigned int bmp085ReadUT(){
  unsigned int ut;

  // Write 0x2E into Register 0xF4
  // This requests a temperature reading
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();

  // Wait at least 4.5ms
  delay(5);

  // Read two bytes from registers 0xF6 and 0xF7
  ut = bmp085ReadInt(0xF6);
  return ut;
}

// Read the uncompensated pressure value
unsigned long bmp085ReadUP(){

  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;

  // Write 0x34+(OSS<<6) into register 0xF4
  // Request a pressure reading w/ oversampling setting
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x34 + (OSS<<6));
  Wire.endTransmission();

  // Wait for conversion, delay time dependent on OSS
  delay(2 + (3<<OSS));

  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  msb = bmp085Read(0xF6);
  lsb = bmp085Read(0xF7);
  xlsb = bmp085Read(0xF8);

  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);

  return up;
}

void writeRegister(int deviceAddress, byte address, byte val) {
  Wire.beginTransmission(deviceAddress); // start transmission to device 
  Wire.write(address);       // send register address
  Wire.write(val);         // send value to write
  Wire.endTransmission();     // end transmission
}

int readRegister(int deviceAddress, byte address){

  int v;
  Wire.beginTransmission(deviceAddress);
  Wire.write(address); // register to read
  Wire.endTransmission();

  Wire.requestFrom(deviceAddress, 1); // read a byte

  while(!Wire.available()) {
    // waiting
  }

  v = Wire.read();
  return v;
}

float calcAltitude(float pressure){

  float A = pressure/101325;
  float B = 1/5.25588;
  float C = pow(A,B);
  C = 1 - C;
  C = C /0.0000225577;

  return C;
}


HMC5883L - Digital Compass

A Digital Compass is a simple magnetometer that is used to calculate the strength of magnetic fields and calculate the direction.


Download the Datasheet Here



Connecting the HMC5883L ( connections are for the module above with model number GY-271 for other modules refer to the module datasheet and connect accordingly)
  • Place the HMC5883L Breakout Board on the breadboard
  • Connect the SDA pin on the HMC5883L to the SDA pin on the Induino R3
  • Connect the SCL pin on the HMC5883L to the SCL pin on the Induino R3
  • Conect the Vcc pin on the HMC5883L to 5Volts on the Induino R3
  • Connect the GND pin on the HMC5883L to the GND pin on the Induino R3
Go to File->Examples->HMC5883L-> and click on HMC5883L_raw



Upload the program and watch the various values for pressure in your serial monitor.


Thats It For This Part! Enjoy... and feel free to drop us an email with questions you might have -> info@simplelabs.co.in

 Visit www.simplelabs.co.in for more interesting products

Thursday, 25 July 2013

I2C Communication - What, Where & How? - Interfacing with the Simple Labs RTC Module

I2C Communication


I2C is short form for 'Inter Integrated Circuit' I2C Communication is Communication Bus standard developed by Phillips for standardising Communication between Integrated Circuits. For Eg. In a circuit, there could be a number of ICs each offering specific functionality[RTC, Temperature Sensor, EEPROM, etc] and they can all communicate on a single I2C Bus and provide combined functionalities. Each device on the I2C Bus would have a unique address by which it can be addressed.

Here's an Interesting Introduction from NXP




I2C on  Induino R3 / Arduino

The I2C Bus uses 2 lines for Communication - SDA(Serial Data) & SCL (Serial Clock). On the Induino R3 / Arduino, these are available on  SDA (Analog Input 4) & SCL (Analog Input 5).  The Induino R3 Also has a I2C Interface to match the Simple Labs RTC Breakout Board.  The I2C bus can be accessed using the 'Wire' Library of Arduino. 

The Simple Labs' RTC Breakout Board
This is a simple breakout board based on the DS1307 RTC IC. The board comes with all the required circuitry for the DS1307 to be interfaced with any microcontroller. It has an on-board Battery holder as well that helps it retain the time settings even when not connected to a microcontroller. If you already don't have the board, You can buy it here - Buy Simple Labs' RTC Breakout Board


Plug in the board as shown below



Programming I2C devices - An Overview

I2C devices are often independent devices that provide us with specific functionality. Each of these devices will have an unique 8-bit address. The 8-bit address limits the number of possible devices on an I2C bus to 255 slaves and 1 Master. In our case, Our ATmega328 would be the master.

The Slave devices provide the the required data through registers. These registers can be accessed / manipulated by the master.
  1. Steps to access data from a device
  2. Create a transmission line with the address of a specific device (so that the device will know that the data is meant for it)  
  3. Write to the device the address of the register we want to access
  4. If we want to write data (set time), Write the Data to the device 1 Byte at a time. The register pointer will keep moving automatically by the number of bytes we write / read.
  5. Close the transmission line (you don't need it to read data)
  6. If we want to read data (get time), Initiate a request to the device, with the number of bytes of data you want to read. It would then read that number of bytes starting from the register location you wrote in step 3 and return it. The returned data is stored in the I2C buffer. 
  7. Read the data from the buffer and process it

Programming the DS1307

While the previous programming guideline is an outline,  the specifics of the data will depend upon the device so you will have to read the datasheet of the device to get a finer understanding. Here are 2 pages from the Datasheet of DS1307 that we need to read before being able to program it. Read it, Read it again, try to understand it before you proceed.

Page 1 of the Datasheet

Page 8 of the Datasheet

Page 8 of the datasheet lists the time keeper registers with their address and what they are used to store. These are the registers which we need to access to set / get time information from the DS1307. Data in these registers is stored in the BCD (Binary Coded Decimal) Format.  Note that different registers are of different size - excepting the years register all the other registers take a 7 bit value.

BCD Format

BCD is a way to represent Decimal Numbers in Binary Format, where each digit of the binary number is represented using a fixed number of bits.  Here's a Quick Example

Decimal Number :                                                                                                 91
Normal Binary Representation of 91 is  =>                                                   0101 1011
For BCD Representation, we split the decimal number into its digits :       9      |       1
and represent the digits in their binary form                                                  1001  |     0001  
So the Binary Representation in BCD would be                                           1001 0001
So to write the decimal number 91 into a register supporting BCD, We need to write the actual decimal value of the Binary Representation in BCD =>  145 (1001 0001)

Re read the above part on BCD again till you are clear ;)

Now if we want to write 91, we need to write 145, but how do we perform this conversion in our program?  Well its simple,

Say 'x' = 91 and 'y' is the value(145) we want to determine, then
 y = (x/10)*16 + (x%10)
 y = (91/10)*16 + (91%10)
 y =  (9*16) + 1
 y =  144 + 1 => 145

Now when we read the registers, we again will get data in the BCD format, we need to convert it to decimal before making use of it. Here's how we can convert the 145 back to 91.

say 'y' = 145 and 'x' is the value(91) we want to determine, then
x = (y/16) *10 + (y%16)
x = (145/16)*10 + (145%16)
x = (9*10) + 1
x = 91

The Hours Register
The data in the hours register of the DS1307 has to be handled differently from that of the other registers. The 6th bit of the hours register is used to determine is the DS1307 is keeping track of time in the 24hr mode or 12hr mode. If it is in the 12 hr mode, then the 5th bit of the hours register is used to keep track of 'AM / PM'. So any time we want to write / read values to/from this register, we need to evaluate these bits and then calculate the actual hour value

So Here our Programming Outline to Set the time (to be done during setup())
  • initialize transmission to the DS1307 (the device address of the DS1307 is 0x68)
  • write the register number to start writing from
  • convert each time parameter to BCD format and then write it to the DS1307
  • for the hour value, check the mode value, get the hour value converted to BCD, Modify the BCD to set the special parameter bits accordingly and finally write the modified BCD value to the DS1307
  • close the transmission line after writing all time parameters
Here's Programming Outline to Get the time (to be done anytime we need to check time)
  • initialize transmission to the DS1307 (the device address of the DS1307 is 0x68)
  • write the register number to start writing from
  • close the transmission line
  • Request 7 bytes of data from the DS1307
  • read each byte of data and then convert it from BCD to normal decimal format and store it in a variable
  • for the hour value, read the 1 byte of data, evaluate the special parameter bits and modify the BCD value accordingly and then Covnert the modified BCD value back to normal decimal and store it in a variable. Also while evaluating the Special Parameter bit, store the time mode in a variable for later use.


Here's a Program which sets and displays the time in the serial monitor

/*  Induino R3 User Guide - Program 11.0 - Interfacing with the Simple Labs DS1307 RTC Module */


#include <Wire.h> // I2C Library

#define myrtc 0x68 // I2C Address of DS1307

char *dow[]={
  " ","MON","TUE","WED","THU","FRI","SAT","SUN"}; // An Array to store the DAY text to match with the DAY parameter of the RTC

char *mode[]={
  "HR","AM","PM"}; // An Array to store the time mode 

int dd,mm,yy,day,hh,mins,ss,mde; // Variables to store the retrieved time value

void setup()
{
  Serial.begin(9600); // Initialise Serial Communication
  Wire.begin(); // Initialise Wire Communication - Join the I2C Bus
  delay(500);
  set_time(31,12,12,7,11,59,50,2); // Call the set_time function to set the intial time.
}

void loop()
{
  get_time();
  Serial.print(dd);
  Serial.print("/");
  Serial.print(mm);
  Serial.print("/");
  Serial.print(yy);
  Serial.print(" "); 
  Serial.print(dow[day]);
  Serial.print(" ");
  Serial.print(hh);
  Serial.print(":"); 
  Serial.print(mins);
  Serial.print(":");
  Serial.print(ss);
  Serial.print(" ");
  Serial.print(mode[mde]);  
  Serial.println();
  delay(1000);
}


// The set_time function takes parameters in the order of date, month, year, day of week, hours, minutes, seconds & mode
// the mode can have 3 possible values  (0=>24HR, 1=> AM, 2 => PM)

void set_time(int sdd, int smm, int syy, int sday, int shr, int smin, int ssec, int smode)
{
  Wire.beginTransmission(myrtc); // Initialise transmission to the myrtc I2C address
  Wire.write(0x00); // Write the value of the register to start with, 0 in this case represented in BCD format
  Wire.write(dec_to_bcd(ssec));  // convert the seconds value from decimal to bcd and write it to the seconds register
  // after the write operation the register pointer will be at the next register, so we do not have to set the value of the register again
  Wire.write(dec_to_bcd(smin));  // convert the minutes value from decimal to bcd and write it to the minutes register
  if(smode == 0) // Check if the mode is 24hrs mode
  {
    Wire.write(dec_to_bcd(shr));  // if 24 hours mode is on then convert the hours value from decimal to bcd and write it to the hours register
  }
  else // if the mode is 12 hr mode
  {
    // If 12 hour mode is selected then the 12 Hour mode bit (the 6th bit) has to be set to 1
    // convert the hour value to bcd first and then adding 64(2^6) to the converted hrs value will set the 6th bit HIGH

    shr = dec_to_bcd(shr)+64; 

    if(smode == 1) // check if it is AM
      Wire.write(shr); // if it is AM we can directly write the value of the above modified hours values to the hours register
    if(smode == 2) // check if it is PM
      Wire.write(shr+32); // If it is PM, then adding 32 (2^5) sets the 5th bit (the PM indication bit) HIGH, the calculated value is written to the hours register
  }
  Wire.write(dec_to_bcd(sday));  // convert the day value from decimal to bcd and write it to the day register
  Wire.write(dec_to_bcd(sdd));  // convert the date value from decimal to bcd and write it to the date register
  Wire.write(dec_to_bcd(smm));  // convert the month value from decimal to bcd and write it to the month register
  Wire.write(dec_to_bcd(syy));// convert the year value from decimal to bcd and write it to the year register
  Wire.endTransmission();  // end the transmission with the I2C device

}


// the get_time() function will retrieve the current time from the RTC and store it in the Global Variables declared

void get_time()
{
  Wire.beginTransmission(myrtc); // Initialise transmission to the myrtc I2C address
  Wire.write(0x00); // Write the value of the register to start with, 0 in this case represented in BCD format
  Wire.endTransmission(); // end the transmission with the I2C device
  Wire.requestFrom(myrtc, 7);  // Now ask the I2C device for 7 Bytes of Data // This corresponds to the values of the 7 registers starting with the 0th register 

  ss = bcd_to_dec(Wire.read()); // The first read will retrieve the value from the register address 0x00 or the seconds register, this is in the BCD format, convert this back to decimal
  mins = bcd_to_dec(Wire.read());// The second read will retrieve the value from the register address 0x01 or the minutes register, this is in the BCD format, convert this back to decimal
  hh = Wire.read();// The third read will retrieve the value from the hours register, this value needs to be processed for the 24/12 hr mode

  // Check of if the BCD hours value retrieved is greater than 35 (this indicates that the hours is in 12 hour mode
  // 35 is the maximum BCD value possible in the 24hr mode
  if(hh > 35) 
  {
    hh = hh - 64; // in the 12 Hours Mode the 12 hour mode bit (6th bit) is set to high, so we need to subtract 2^6 from our hours value
    if(hh > 32)// Now check if the hour value is greater than 32 (2^5 = 32) (this indicates that PM bit (5th bit) is high)
    {
      mde = 2; // Set the mde variable to indicate PM
      hh = hh-32; // subtract 32 from the hours value 
    }
    else // if the hour value is less than 32 it means that its in the AM mode
    {
      mde = 1; // Set the mde variable to indicate AM
    }   
  }
  else // if the 12 hour mode bit was not set, then the hour is in the 24 hour mode
  {
    mde = 0; // Set the mde variable to indicate 24 Hours
  }

  hh = bcd_to_dec(hh); // Convert the final hour value from BCD to decimal and store it back into the same variable
  day = bcd_to_dec(Wire.read());// The fourth read will retrieve the value from the register address 0x03 or the day register, this is in the BCD format, convert this back to decimal
  dd = bcd_to_dec(Wire.read());// The fifthread will retrieve the value from the register address 0x04 or the date register, this is in the BCD format, convert this back to decimal
  mm = bcd_to_dec(Wire.read());// The sixth read will retrieve the value from the register address 0x05 or the month register, this is in the BCD format, convert this back to decimal
  yy = bcd_to_dec(Wire.read());// The seventh read will retrieve the value from the register address 0x06 or the year register, this is in the BCD format, convert this back to decimal

}


// The dec_to_bcd() function converts a given decimal number to BCD format

int dec_to_bcd(int dec)
{
  return dec/10*16 + (dec%10); // convert and return the number from decimal to bcd format
}



// The dec_to_bcd() function converts a given BCD number to decimal format

int bcd_to_dec(int bcd)
{
  return bcd/16*10 + (bcd%16); // convert and return the number from bcd to decimal format
}

Here's another interesting Program that uses the button on pin 7 to enable / disable an Alarm that goes on a particular day at a particular time. This program uses the LCD to display the time.

/*  Induino R3 User Guide - Program 11.1 - Interfacing with the Simple Labs DS1307 RTC Module to Display time on the LCD using the LCD Shield */


#include <Wire.h> // I2C Library
#include <LiquidCrystal.h> // LiquidCrystal Library for LCD

#define myrtc 0x68 // I2C Address of DS1307

char *dow[]={
  " ","MON","TUE","WED","THU","FRI","SAT","SUN"}; // An Array to store the DAY text to match with the DAY parameter of the RTC

char *mode[]={
  "HR","AM","PM"}; // An Array to store the time mode 

char *month[]={
  "","JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"};

int dd,mm,yy,day,hh,mins,ss,mde; // Variables to store the retrieved time value

const int alr_day = 4, alr_hour=12, alr_hour_mode =1, alr_min=0;

boolean alarm_flag = 0; // stores the current state of the alarm 0-> Off / 1-> On
int match_cnt = 0; // This helps us ensure that alarm goes on only on the first instance of the time matching. 

LiquidCrystal lcd(8,9,10,11,12,13); // Create an object of LiquidCrystal class

void setup()
{
  Wire.begin(); // Initialise Wire Communication - Join the I2C Bus
  lcd.begin(16, 2);  // Initialise Communication with LCD
  delay(500);
  set_time(31,12,12,3,11,59,55,2); // Call the set_time function to set the intial time.
  pinMode(7,INPUT_PULLUP); // pinMode for the button pin
}

void loop()
{
  if(digitalRead(7)==0) // Check if the push button is being pressed and toggle the alarm state
  {
    alarm_flag=!alarm_flag;
  }

  get_time();// get the current time from the RTC

  lcd.clear(); // Clear the Display

  //Check if the time matches the alarm time and if the alarm_flag is on and that the first instance of the match has not yet occured
  if(alr_day==day && alr_hour == hh && alr_hour_mode==mde && alr_min==mins && alarm_flag == 1 && match_cnt==0)
  { 
    // set the match count to 1 as we have encountered a time match, this will ensure that within the set minute the alarm goes on only once
    // otherwise the alarm will be on for the whole minute
    match_cnt=1; 

    // A Simple LCD Blink to indicate ALARM
    for(int cnt=0;cnt<5;cnt++)
    {
      lcd.setCursor(5,0);
      lcd.print("ALARM!");
      delay(500);
      lcd.clear();
      delay(500);
    }
  }  
  else // Incase the Time does not match the alarm time then display time as usual
  {  
    lcd.print(dd);
    lcd.print("-");
    lcd.print(month[mm]);
    lcd.print("-");
    lcd.print(yy);
    lcd.setCursor(10,0); // Set the Cursor at a specific location to print the day of the week
    lcd.print(dow[day]);

    lcd.setCursor(0,1);
    lcd.print(hh);
    lcd.print(":");
    lcd.print(mins);
    lcd.print(":");
    lcd.print(ss);
    lcd.setCursor(11,1);
    lcd.print(mode[mde]);// Set the Cursor at a specific location to print the hour mode

    if(alarm_flag == 1) // check if the alarm flag is on and display alarm on indication
    {
      lcd.setCursor(14,1);
      lcd.print("AL");
    }
    delay(1000);
  }
  if(alr_day==day && alr_hour == hh && alr_hour_mode==mde && (alr_min+1)==mins) // check if the set minute has elapsed and reset the match_cnt
  {
    match_cnt=0;
  }
}


// The set_time function takes parameters in the order of date, month, year, day of week, hours, minutes, seconds & mode
// the mode can have 3 possible values  (0=>24HR, 1=> AM, 2 => PM)

void set_time(int sdd, int smm, int syy, int sday, int shr, int smin, int ssec, int smode)
{
  Wire.beginTransmission(myrtc); // Initialise transmission to the myrtc I2C address
  Wire.write(0x00); // Write the value of the register to start with, 0 in this case represented in BCD format
  Wire.write(dec_to_bcd(ssec));  // convert the seconds value from decimal to bcd and write it to the seconds register
  // after the write operation the register pointer will be at the next register, so we do not have to set the value of the register again
  Wire.write(dec_to_bcd(smin));  // convert the minutes value from decimal to bcd and write it to the minutes register
  if(smode == 0) // Check if the mode is 24hrs mode
  {
    Wire.write(dec_to_bcd(shr));  // if 24 hours mode is on then convert the hours value from decimal to bcd and write it to the hours register
  }
  else // if the mode is 12 hr mode
  {
    // If 12 hour mode is selected then the 12 Hour mode bit (the 6th bit) has to be set to 1
    // convert the hour value to bcd first and then adding 64(2^6) to the converted hrs value will set the 6th bit HIGH

    shr = dec_to_bcd(shr)+64; 

    if(smode == 1) // check if it is AM
      Wire.write(shr); // if it is AM we can directly write the value of the above modified hours values to the hours register
    if(smode == 2) // check if it is PM
      Wire.write(shr+32); // If it is PM, then adding 32 (2^5) sets the 5th bit (the PM indication bit) HIGH, the calculated value is written to the hours register
  }
  Wire.write(dec_to_bcd(sday));  // convert the day value from decimal to bcd and write it to the day register
  Wire.write(dec_to_bcd(sdd));  // convert the date value from decimal to bcd and write it to the date register
  Wire.write(dec_to_bcd(smm));  // convert the month value from decimal to bcd and write it to the month register
  Wire.write(dec_to_bcd(syy));// convert the year value from decimal to bcd and write it to the year register
  Wire.endTransmission();  // end the transmission with the I2C device

}


// the get_time() function will retrieve the current time from the RTC and store it in the Global Variables declared

void get_time()
{
  Wire.beginTransmission(myrtc); // Initialise transmission to the myrtc I2C address
  Wire.write(0x00); // Write the value of the register to start with, 0 in this case represented in BCD format
  Wire.endTransmission(); // end the transmission with the I2C device
  Wire.requestFrom(myrtc, 7);  // Now ask the I2C device for 7 Bytes of Data // This corresponds to the values of the 7 registers starting with the 0th register 

  ss = bcd_to_dec(Wire.read()); // The first read will retrieve the value from the register address 0x00 or the seconds register, this is in the BCD format, convert this back to decimal
  mins = bcd_to_dec(Wire.read());// The second read will retrieve the value from the register address 0x01 or the minutes register, this is in the BCD format, convert this back to decimal
  hh = Wire.read();// The third read will retrieve the value from the hours register, this value needs to be processed for the 24/12 hr mode

  // Check of if the BCD hours value retrieved is greater than 35 (this indicates that the hours is in 12 hour mode
  // 35 is the maximum BCD value possible in the 24hr mode
  if(hh > 35) 
  {
    hh = hh - 64; // in the 12 Hours Mode the 12 hour mode bit (6th bit) is set to high, so we need to subtract 2^6 from our hours value
    if(hh > 32)// Now check if the hour value is greater than 32 (2^5 = 32) (this indicates that PM bit (5th bit) is high)
    {
      mde = 2; // Set the mde variable to indicate PM
      hh = hh-32; // subtract 32 from the hours value 
    }
    else // if the hour value is less than 32 it means that its in the AM mode
    {
      mde = 1; // Set the mde variable to indicate AM
    }   
  }
  else // if the 12 hour mode bit was not set, then the hour is in the 24 hour mode
  {
    mde = 0; // Set the mde variable to indicate 24 Hours
  }

  hh = bcd_to_dec(hh); // Convert the final hour value from BCD to decimal and store it back into the same variable
  day = bcd_to_dec(Wire.read());// The fourth read will retrieve the value from the register address 0x03 or the day register, this is in the BCD format, convert this back to decimal
  dd = bcd_to_dec(Wire.read());// The fifthread will retrieve the value from the register address 0x04 or the date register, this is in the BCD format, convert this back to decimal
  mm = bcd_to_dec(Wire.read());// The sixth read will retrieve the value from the register address 0x05 or the month register, this is in the BCD format, convert this back to decimal
  yy = bcd_to_dec(Wire.read());// The seventh read will retrieve the value from the register address 0x06 or the year register, this is in the BCD format, convert this back to decimal

}


// The dec_to_bcd() function converts a given decimal number to BCD format

int dec_to_bcd(int dec)
{
  return dec/10*16 + (dec%10); // convert and return the number from decimal to bcd format
}



// The dec_to_bcd() function converts a given BCD number to decimal format

int bcd_to_dec(int bcd)
{
  return bcd/16*10 + (bcd%16); // convert and return the number from bcd to decimal format
}


Thats It For This Part! Enjoy... and feel free to drop us an email with questions you might have -> info@simplelabs.co.in

 Visit www.simplelabs.co.in for more interesting products

  Back to List of Contents

Monday, 22 July 2013

LCD Interfacing - Working with the Simple Labs' LCD Shield

The Simple Labs' LCD Shield

The LCD Shield eases the process of interfacing a LCD display with the Induino R3 / Arduino. The shield fits on top of any Arduino Board  and takes power from the Arduino Board.


The LCD shield is designed to work with 16 pin LCDs with the following pin configuration.
















LCD Pin Mappings for the Shield
The LCD shields provides a 4-bit mode interface to the Arduino. The Pins are connected as follows. 
RS => 8
E => 9
D4 =>10
D5 =>11
D6 =>12
D7 =>13


How to place the Shield on top of an Arduino?
The shield has one of set 8 pins and 1 set of 6 Pins. The trimpot on the shield is on the top left side. Now place the shield onto your arduino such that the eight pins are placed on top of pins 8,9,10,11,12,13,gnd & aref and the six pins are placed on top of pins Vin, gnd, gnd, +5V, +3.3V & reset


Working of an LCD
Here is a neat write up on this, We suggest you read it completely before proceeding and Don't worry about the programming part given in the write up, we have a library that will take care of most of it for us ;) http://joshuagalloway.com/lcd.html

Programming the LCD Shield

Arduino IDE has useful LCD library - LiquidCrystal - This library helps us interface character lcds with ease.  The library supports both 8-bit and 4-bit mode of working with LCDs. If you had read the above write up,  its our good bet that all those programming how to got to your head. Well, the LiquidCrystal library takes it all away for us.

  • Include the LiquidCrystal.h Library
  • Create a Object of Class LiquidCrystal (can have any name like a variable) / Syntax - LiquidCrystal lcd(pin numbers). pin numbers are to be in the following order 
    • 8-bit mode with R/W not being grounded => RS, R/W, E, D0, D1, D2, D3, D4, D5, D6, D7
    • 8-bit mode with R/W directly grounded => RS, E, D0, D1, D2, D3, D4, D5, D6, D7
    • 4-bit mode with R/W not being grounded => RS, R/W, E, D4, D5, D6, D7
    • 4-bit mode with R/W directly grounded (for our LCD Shield) => RS, E, D4, D5, D6, D7
  • So our initialization will look like -> LiquidCrystal lcd(8,9,10,11,12,13);
  • Initialize communication with the LCD using the lcd.begin(Characters per line,lines) function. The lcd.begin()function takes 2 parameters - the number of Characters per line and number of lines. in our case it would be 16 characters per line and 2 lines. so => lcd.begin(16,2);
  • dsiplay data on the lcd using the lcd.print(data) function. The lcd.print() function takes the data to be displayed as parameter and prints it.


Here's a program to display the LDR value onto the LCD

/*  Induino R3 User Guide - Program 10.0 - Interfacing a Character LCD using the LCD Shield to display LDR value on the LCD */
/* Pin Mappings as per the Simple Labs' LCD shield
 * LCD RS pin to digital pin 8
 * LCD Enable pin to digital pin 9
 * LCD D4 pin to digital pin 10
 * LCD D5 pin to digital pin 11
 * LCD D6 pin to digital pin 12
 * LCD D7 pin to digital pin 13
 * LCD R/W pin to ground
*/

// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins from the above pin mappings
LiquidCrystal lcd(8,9,10,11,12,13); // 

void setup() {
  // set up the LCD's number of characters per line and lines: 
  lcd.begin(16, 2);

}

void loop() {
  
  lcd.clear();//Clears the Display - If you don't clear, previous data will remain as it is
  int ldr_val = analogRead(3); // read the LDR value
  lcd.print("LDR Value : "); // print the description text
  lcd.print(ldr_value); // print the actual value
  delay(2000); // wait before refreshing the value
  
}


There are a number of other functions available to work with the LCD. These are documented here on the official Arudino Refernce Page -> http://arduino.cc/en/Reference/LiquidCrystal

Feel free to try out all the examples in the LiquidCrystal library. Just remember to change the pin numbers during the object creation statement -> LiquidCrystal lcd(8,9,10,11,12,13);

Here's another program to display the Distance Value based on the HCSR 04 onto the LCD

/*  Induino R3 User Guide - Program 10.1 - Interfacing a Character LCD using the LCD Shield to display Distance value on the LCD */
/* Pin Mappings as per the Simple Labs' LCD shield
 * LCD RS pin to digital pin 8
 * LCD Enable pin to digital pin 9
 * LCD D4 pin to digital pin 10
 * LCD D5 pin to digital pin 11
 * LCD D6 pin to digital pin 12
 * LCD D7 pin to digital pin 13
 * LCD R/W pin to ground
*/
// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8,9,10,11,12,13);

const int pingPin = 16; // HCSR-04 is connected to the sensor Interface A2 aka 16

long ol_inches=0, ol_cm=0;

void setup() {
  // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);

}

void loop() {
  // establish variables for duration of the ping, 
  // and the distance result in inches and centimeters:
  long duration, inches, cm;

  // The HCSR 04 is triggered by a HIGH pulse of 2 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);

  // The same pin is used to read the signal from the HCSR 04: a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(pingPin, INPUT);
  duration = pulseIn(pingPin, HIGH);

  // convert the time into a distance
  inches = microsecondsToInches(duration);
  cm = microsecondsToCentimeters(duration);
  
  //if(cm!=ol_cm || inches!=ol_inches)  // Check if the new dsitance values are not the same as the old values
  {
  lcd.clear(); // Clear the Dispaly
  lcd.print(inches); // Print the Distance in Inches
  lcd.setCursor(4, 0); // Position the cursor of the LCD display to print from the 5th Character on the First Line
  lcd.print("INCHES");// Print the descriptive text
  lcd.setCursor(0, 1); // Position the cursor of the LCD display to print from the 1st Character on the Second Line
  lcd.print(cm); // Print the Distance in Centimeters
  lcd.setCursor(4, 1);// Position the cursor of the LCD display to print from the 5th Character on the Second Line
  lcd.print("CMS"); // Print the descriptive text
  ol_inches = inches; // store the new inches value into a variable
  ol_cm = cm; // store the new centimeters value into a variable
  delay(200);
  
}
  
  // print the number of seconds since reset:
}

long microsecondsToInches(long microseconds)
{
  // According to Parallax's datasheet for the PING))), there are
  // 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
  // second).  This gives the distance travelled by the ping, outbound
  // and return, so we divide by 2 to get the distance of the obstacle.
  // See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
  return microseconds / 74 / 2;
}

long microsecondsToCentimeters(long microseconds)
{
  // The speed of sound is 340 m/s or 29 microseconds per centimeter.
  // The ping travels out and back, so to find the distance of the
  // object we take half of the distance travelled.
  return microseconds / 29 / 2;
}

That's It For This Part! Enjoy... and feel free to drop us an email with questions you might have -> info@simplelabs.co.in

Visit www.simplelabs.co.in for more interesting products

Back to List of Contents

Sunday, 21 July 2013

Servo Interfacing - Working with the on-board Servo Interface

Induino R3  On-Board Servo Interface


The Induino R3 has 2 On-Board Interfaces for controlling Servo Motors. This is Next to the Sensor Interface on the Induino R3 Board.  The Servo interface has a +5V supply line for the servos that is being shared with the microcontroller. The Pins A4-18 / A5-19 are the Signal Pins. 



Servo Motors - What are they?

Servo motors are dc motors with a servo mechanism that lets us control the precise position of the Shaft.

A servo mechanism is a error correction mechanism that senses the negative error and applies a correction accordingly.

There are many types of Servo Motors available - depending upon applications.
They are used in areas requiring position control. While the basic working of all the servo motors is almost similar, for the scope of this tutorial we shall stick to discussing RC Servo Motors or the type of Servo motors used in Robotics / Radio Control Projects.


A Servo motor constitutes of 4 parts - a DC motor, a Gear system, A Potentiometer(for sensing feedback) and a Control Circuit. Here the Control Circuit and the Potentiometer together form the Servo Mechanism.





So How do Servo Motors Work?

The Control Circuitry on the Servo motor drives the DC motor based on input signals it receives, The output shaft of the DC motor is connected to the gear assembly and the gear assembly is such that it moves the potentiometer when it rotates. The Control Circuitry gets feedback from this potentiometer and spins the DC motor to achieve the desired angle between 0 & 180 degrees. The range of movement of the potentiometer is mechanically restrained.

And How do we Control these Servo Motors?

To Control a servo we need to give it a pulse once every 20 milliseconds. The Duration of this pulse will determine the Servo Angle which we need to achieve. For most Servo's a Pulse duration of 1millisecond will set the shaft position to 0 degrees and a pulse duration of 2 milliseconds will set the shaft position to 180 degrees. 



Controlling Servos with an Arduino

Arduino provides us with a servo control library that lets us control servos with ease. The library provides us with an option of writing both microseconds value and angle values. You can find more information on the library here - > Arduino Servo Library

Connecting the Servo Motors

First Identify the '+','GND' & 'Data'  Lines of your Servo Motor based on the image below

Connect the Servo Motor to the Servo Interface on the Induino R3 Board as shown in the image below
t

Ensure you use an External DC Adapter to Power the Board When Working with Servos
Programming the Servo
  • Include the Servo.h Library
  • Create a Object of Class Servo (can have any name like a variable) / Syntax - Servo myservo;
  • Map the Servo Object to a Pin using the attach() function of the Servo Library -> myservo.attach(servo pin number);
  • Control the Servo using the write() function of the Servo Library. the write() function takes in a degree value in the range of 0 to 179 as input parameter and creates the required control signal on the servo pin.


Here's a modified version of the Sweep Example from the Servo Library

/*  Induino R3 User Guide - Program 9.0 - Controlling a Servo Motor Using the Servo Interface on the Induino R3 */

#include  <Servo.h> 
 
Servo myservo;  // create servo object to control a servo 
                // a maximum of eight servo objects can be created 
 
int pos = 0;    // variable to store the servo position 
 
void setup() 
{ 
  myservo.attach(18);  // attaches the servo on pin 18 to the servo object 
} 
 
 
void loop() 
{ 
  for(pos = 0; pos < 180; pos += 1)  // goes from 0 degrees to 180 degrees 
  {                                  // in steps of 1 degree 
    myservo.write(pos);  // tell servo to go to position in variable 'pos' 
    delay(15);    // waits 15ms for the servo to reach the position 
  } 
  for(pos = 180; pos>=1; pos-=1) // goes from 180 degrees to 0 degrees 
  {                                
    myservo.write(pos); // tell servo to go to position in variable 'pos' 
    delay(15);          // waits 15ms for the servo to reach the position 
  } 
}  

Next Let's see how we can control the Servo using the Light Intensity sensed by the On-Board LDR.
The LDR will give us an analog value in the range of 0 - 1023. However the maximum value that can be given to the write() function is 179.  So we need to scale down the values. For this, we have an inbuilt function in Arduino


map(value, fromLow, fromHigh, toLow, toHigh) - The map() function re-maps a number from one range to another. That is, a value of fromLow would get mapped to toLow, a value of fromHigh to toHigh, values in-between to values in-between, etc.

Here's our program where the LDR value is mapped to control the Servo

/*  Induino R3 User Guide - Program 9.1 - Controlling a Servo Motor Using the LDR Value */


#include <Servo.h> 
 
Servo myservo;  // create servo object to control a servo 
 
int ldr_pin = 3;  // ldr is on Analog pin 3
int val;    // variable to read the value from the analog pin 
 
void setup() 
{ 
  myservo.attach(18);  // attaches the servo on pin 9 to the servo object 
} 
 
void loop() 
{ 
  val = analogRead(ldr_pin);      // reads the value of the LDR (value between 0 and 1023) 
  val = map(val, 0, 1023, 0, 179); // scale it to use it with the servo (value between 0 and 180) 
  myservo.write(val); // sets the servo position according to the scaled value 
  delay(15);   // waits for the servo to get there 
} 


Thats It For This Part! Enjoy... and feel free to drop us an email with questions you might have -> info@simplelabs.co.in

Visit www.simplelabs.co.in for more interesting products

Back to List of Contents

Generating Remote Control Signals using the on-board IR LED

The on-board IR LED

We have an on-board IR LED on the Induino R3 Board. When we were designing the first Induino Board, we thought it would be fun to be able to control a television from the board and ended up adding the IR LED to the board. On top of being able to control devices with remote control, the IR LED can be combined with the TSOP to build a simple wireless communication system. However to try the wireless communication system, you would require two Induino R3 Boards.

The IR LED is connected to digital pin 14 (A0).  A Few things to note about the IR LED 
  • It is not a visible spectrum LED, so you will not be able to see it glowing with your naked eyes, if you look at the IR LED through a camera, you will be able to spot a Pink glow - that's the IR LED glowing. 
  • The IR LED on the Induino R3 board is driven by a simple driver circuitry and might not be as powerful as the one in your remote. You might have to point it in a straight line to get it working.
Understanding Frequency
The Sony SIRC protocol works at 40Khz...most remote controls work at their specific frequencies. TV remotes in India are generally between 32- 40Khz frequecny.

So whats this frequency all about?
40KHz means 40,000 Cycles in 1 second. Each Cycle represents a ON signal followed by a OFF signal. The signals could be of equal duration.

Here's another image of the Sony signal train

If you look at the image, you can see the the 1.2ms high of the Logical '1' has further black lines with spaces in between. These correspond to the ON/OFF cycles. The space between these is what is called the frequency. The frequency of occurrence of a ON/OFF cycle is what it means.

So How do we generate it?
Lets do some calculation,
40,000 Cycles in 1 Seconds => 1000 milliseconds  => 1000 x 1000 microseconds
so each cycle if for a duration of 25 microseconds.

We can produce this frequency if we can make a pin go high for 13 microseconds followed by low for 12 microseconds.

If we can do this for 2.4 milliseconds then we can generate a Start bit, if we can do this for 1.2 milliseconds then we can generate a Logical '1', and for 0.6 milliseconds, we can generate a Logical '0'.

Now that we know that each cycle will take 25 microseconds lets calculate the number of cycles required for generating the 2.4 milliseconds start bit

2.4 milliseconds / 25microseconds => 2400 microseconds / 25 microseconds => 96 cycles
1.2 milliseconds / 25microseconds => 1200 microseconds / 25 microseconds => 48 cycles
0.6 milliseconds / 25microseconds => 600 microseconds / 25 microseconds => 24 cycles

If you noticed, we are working with microseconds, we would need a new function to work with these

delaymicroSeconds(time in microseconds) => the delayMicroseconds() function takes a parameter and produces a corresponding delay in microseconds.

Now Here's how we are going to go ahead with our Programming

  1. Take the value to be transmitted convert it to a 12-bit binary value and store the binary value in an array
  2. Process the array going though 1 element at a time, and based on the element ('1' or '0') called a function to control the IR led for a given number of cycles
Since we will be working with Binary Numbers,  We shall use the boolean data type.
boolean - boolean is a datatype used for storing values '1' or '0' (true or false)

Let's first write the function to control the IR LED to pulse for a given number of cycles. Here's the function. The transmit() function will take the number of cycles as a parameter and create a 40KHZ Pulse for that many cycles. Now the Cycle time for each cycle has to be 25 microseconds, keeping this in mind we will have to compensate for the time taken by each statement to execute as well

void transmit(int number)
{
  // each execution of this 'for loop' takes 10 microseconds, so our delay timings are adjusted accordinly
  // each execution of this 'for loop' should correspond to the duration of 1 cycle
  // for Sony protocol our cycle duration is 25microseconds, so we have delays of 8 + 7 + 10(total statement execution time for all statements inside)
  for(int i=0; i<number;i++) 
  {
    digitalWrite(14,HIGH);
    delayMicroseconds(8);
    digitalWrite(14,LOW);
    delayMicroseconds(7);
  }
  delayMicroseconds(600); // this is the delay corresponding the the Space timing between 2 pulses
}

Now this is a basic transmit function, we would need an additional function that would take data, convert it to binary and then based on the binary values call the transmit function with the appropriate cycle values.  Here's a function for the same

void cnvrt_n_snd(int data)
{
  // The first for loop is for converting the data into a 12-bit binary value and storing it in an array
   for(int i=0;i<12;i++)
  {
    binary_array[i] = data % 2; // get the reminder and store in an array
    data= data/2;
  }
  // First send the Startbit, Then navigate through the Array, check each bit value and transmit a signal accordingly
  transmit(96); // Transmit Start Bit
  // If you are using the IR Remote Library to receive at the receiver change the for loop below like this  for(int i=11;i>=0;i--)
  for(int i=0;i<12;i++)
  {
    if( binary_array[i]== 1) // Check if the Binary value is '1' if so trasnmit the signal for '1' else transmit the signal for '0'
      transmit(48);
    else 
      transmit(24);
  }
}

Note: In the above function, the second for loop has to be changed to for(int i=11;i>=0;i--), if you are using the IR remote library at the receiving end.  This is because the IR remote library process the 12-bit data in a different way, it takes the first data received as the MSB and the last data as LSB. Whereas our custom program takes it the other way.

Here's a program to work with one of our previous programs 7.1 (Remote Controlled Binary Counter with our Custom Receiver Program) Load this program into one Induino R3 board and the other Program into another Induino R3 Board. Place the boards Apart with the IR LED of One Board facing the TSOP of the other and watch the binary counter increment automatically.

/*  Induino R3 User Guide - Program 8.0 -  IR LED for generating Sony Remote Control Signals */

boolean binary_array[12]; // A boolean array for storing the converted binary values

void setup()
{
  pinMode(14,OUTPUT); // define the IR LED pin as output
}

void loop()
{
  cnvrt_n_snd(144); // Transmitting the Value of the Ch UP button
  delay(1000);
}

void transmit(int number)
{
  // each execution of this 'for loop' takes 10 microseconds, so our delay timings are adjusted accordinly
  // each execution of this 'for loop' should correspond to the duration of 1 cycle
  // for Sony protocol our cycle duration is 25microseconds, so we have delays of 8 + 7 + 10(total statement execution time for all statements inside)
  for(int i=0; i<number;i++) 
  {
    digitalWrite(14,HIGH);
    delayMicroseconds(8);
    digitalWrite(14,LOW);
    delayMicroseconds(7);
  }
  delayMicroseconds(600); // this is the delay corresponding the the Space timing between 2 pulses
}

void cnvrt_n_snd(int data)
{
  // The first for loop is for converting the data into a 12-bit binary value and storing it in an array
   for(int i=0;i<12;i++)
  {
    binary_array[i] = data % 2; // get the reminder and store in an array
    data= data/2; // Divide the number by 2
  }
  // First send the Startbit, Then navigate through the Array, check each bit value and transmit a signal accordingly
  transmit(96); // Transmit Start Bit
  // If you are using the IR Remote Library to receive at the receiver change the for loop below like this  for(int i=11;i>=0;i--)
  for(int i=0;i<12;i++)
  {
    if( binary_array[i]== 1) // Check if the Binary value is '1' if so transmit the signal for '1' else transmit the signal for '0'
      transmit(48);
    else 
      transmit(24);
  }
}

With a slight modification to the above Program, we can transmit the LDR value. Ensure the receiver program is 7.0 - TSOP Remote Receiver Program for Sony Remote

/*  Induino R3 User Guide - Program 8.1 -  IR LED for Trasnmitting the LDR Value */

boolean binary_array[12]; // A boolean array for storing the converted binary values

void setup()
{
  pinMode(14,OUTPUT); // define the IR LED pin as output
}

void loop()
{
  
  cnvrt_n_snd(analogRead(3)); // Transmitting the Value of the LDR
  delay(1000);
}

void transmit(int number)
{
  // each execution of this 'for loop' takes 10 microseconds, so our delay timings are adjusted accordinly
  // each execution of this 'for loop' should correspond to the duration of 1 cycle
  // for Sony protocol our cycle duration is 25microseconds, so we have delays of 8 + 7 + 10(total statement execution time for all statements inside)
  for(int i=0; i<number;i++) 
  {
    digitalWrite(14,HIGH);
    delayMicroseconds(8);
    digitalWrite(14,LOW);
    delayMicroseconds(7);
  }
  delayMicroseconds(600); // this is the delay corresponding the the Space timing between 2 pulses
}

void cnvrt_n_snd(int data)
{
  // The first for loop is for converting the data into a 12-bit binary value and storing it in an array
   for(int i=0;i<12;i++)
  {
    binary_array[i] = data % 2; // get the reminder and store in an array
    data= data/2; // Divide the number by 2
  }
  // First send the Startbit, Then navigate through the Array, check each bit value and transmit a signal accordingly
  transmit(96); // Transmit Start Bit
  // If you are using the IR Remote Library to receive at the receiver change the for loop below like this  for(int i=11;i>=0;i--)
  for(int i=0;i<12;i++)
  {
    if( binary_array[i]== 1) // Check if the Binary value is '1' if so trasnmit the signal for '1' else transmit the signal for '0'
      transmit(48);
    else 
      transmit(24);
  }
}



Thats It For This Part! Enjoy... and feel free to drop us an email with questions you might have -> info@simplelabs.co.in

Visit www.simplelabs.co.in for more interesting products

Back to List of Contents

The On-Board Sensor Interface & Interfacing External Sensors - Variable Resistors, LM35 & HC-SR04

The On-Board Sensor Interface & Interfacing External Sensors


The Induino R3 Board has an on-board sensor interface. The Sensor interface is to the Right of the microcontroller above the TSOP. If you look at it, it has pins in the following order
+5 - GND - A2  - A2 - +5 - GND


Connecting Other Variable Resistors - Using An External Resistor

Now to connect any other variable resistor type sensor in the voltage divider configuration, place one leg of the fixed resistor in GND and another leg in the A2 pin next to ground. Then place the variable resistor sensor between the next A2 Pin and +5V pin next to it.  That's it you are set to go. Look at this image of how we have connected a Force Sensitive Resistor. The programming for this would be the same as that of the LDR.



Interfacing a LM 35 Temperature Sensor

The LM35 is a temperature sensor that output a 10 millivolt / per degree C output. (Read the first page of the datasheet here). Here's a pin diagram for the LM35. The Flat side is facing up in the image.

Now Place the LM35 on the Sensor Interface Pins as shown in the image below.

Bend the Gnd Pin (3rd Pin) a Little As Shown

Place the LM35 as Shown. This is the Flat Side of the LM35 Facing You. Note that we have skipped 1 pin in the sensor interface and plugged the LM35

The Curved Side View of LM35 Placement.

Let's get to the programming now. The LM35 gives a linear output of 10 miilivolt per degree Celsius. It can measure temperature in the range of -55 to 150 degrees.  Our Analog Input Resolution is 10 bits. So we will be getting 0 to 1023 values for 0 to 5 volts.
0 to 1023 => 0 to 5 Volts

Therefore 1 Volt => 204.6  => 204 (this is the numerical value that represents 1 volt)
1 Volts => 1000 miiliVolt, So lets rewrite

1000 millivolt => 204
1 degree Celsius => 10 millivolts =>2.04 or 2 (So the numerical value 2 corresponds to 1 degree C )

So every increase in the numerical value signifies an increase in temperature by 0.5 degree. We shall  need to use this derivation in our calculation.

Here's our Program

/* Induino R3 User Guide - Program 6.0 - Simple LM 35 Temperature Sensor Interfacing */

float temp_val = 0; // variable to store the temperature value
void setup()-
{
  Serial.begin(9600); // Initialise Serial Communication
}

void loop()
{
  int sensor_val = analogRead(2); // Read the raw sensor value and store it in the variable
  temp_val = sensor_val * 0.5; // The calculated value
  Serial.print("Current Temperature : ");
  Serial.println(temp_value); // print the temperature value to serial monitor
  delay(1000); // a delay - do not remove this delay (you can change it) as otherwise the serial monitor will get flooded with data and might crash
}

Increasing Precision

If you notice in the above program, our temperature measurement has a precision in the range of 0.5 degrees. What do we do if we require a  finer precision, say  0.1 degree? How do we get it?

Well if you noticed, the temperature range of LM35 is 150 degrees maximum  which corresponds to 1.5 volts, the LM35 will never output a value beyond 1.5 volts.  Now what if we can set the higher limit for our voltage value as 1.5 volts instead of 5 Volts? We would get an increased precision.

Analog Reference Voltage

Our ATmega 328 uses something called an Analog Reference Voltage. This is the voltage that is used as higher reference limit when working with Analog Inputs. By default this is the same as the operating voltage (5 Volts) however this can be over-ridden. You can set an Analog Reference Voltage other than the operating voltage in the following ways

Using the AREF Pin (below the GND pin below 13th Pin) - Connect an input producing any voltage but not exceeding the operating voltage. When an external voltage is connected to this pin, that voltage will be considered as the reference voltage.  You can try by connecting a wire from the 3.3 Volts pin to the AREF Pin.  Now,

3.3 Volts => 1023 Values
3300 Millivolts => 1023 Values 
1 Degree => 10 Millivolts => 1023/330 Values => 3.1 or 3 Values
or 1 Value will correspond to 1/3 => 0.33 degrees 

You can see that our precision has increased. Here's the program with calculation for 3.3V reference voltage

/* Induino R3 User Guide - Program 6.1 - LM 35 Temperature Sensor with Extenral Analog Reference Voltage of 3.3 volts*/

float temp_val = 0; // variable to store the temperature value

void setup()-
{
  Serial.begin(9600); // Initialise Serial Communication
}

void loop()
{
  int sensor_val = analogRead(2); // Read the raw sensor value and store it in the variable
  temp_val = sensor_val * 0.33; // The calculated value
  Serial.print("Current Temperature : ");
  Serial.println(temp_value); // print the temperature value to serial monitor
  delay(1000); // a delay - do not remove this delay (you can change it) as otherwise the serial monitor will get flooded with data and might crash
}

Using the Internal Reference - The ATmega328 has the option to set the Analog Reference to Internal 1.1 volts. This can be done in the program. This would give us more precision with the LM35

0 to 1.1 volts => 0 - 1023 Values
1100 millivolts => 1023
1 Degree => 10 millivolts => 1023 / 110 => 9.3 or 9
or 1 Value will correspond to 1/9 => 0.11 degree

Now thats more precise than what we started with.

analogReference(INTERNAL) => This function sets the analog reference voltage to the internal 1.1 Volts

Heres the program for that

/* Induino R3 User Guide - Program 6.2 - LM 35 Temperature Sensor with Internal Analog Reference Voltage of 3.3 volts*/

float temp_val = 0; // variable to store the temperature value

void setup()
{
  Serial.begin(9600); // Initialise Serial Communication
  analogReference(INTERNAL); // Setup the Reference Voltage as Internal
}

void loop()
{
  int sensor_val = analogRead(2); // Read the raw sensor value and store it in the variable
  temp_val = sensor_val * 0.11; // The calculated value
  Serial.print("Current Temperature : ");
  Serial.println(temp_value); // print the temperature value to serial monitor
  delay(1000); // a delay - do not remove this delay (you can change it) as otherwise the serial monitor will get flooded with data and might crash
}

Interfacing the HC-SR04 Ultrasonic Sensor

The HC-SR04 is a low cost ultrasonic sensor built along the lines of a PING Sensor.  When triggered, It sends out an ultrasonic sound wave through the ultrasonic transmitter and starts a timer,   The sound wave travels in a straigh line till it hits an obsctacle that reflects it back.  The reflected sound wave is received by the ultrasonic receiver. The timer is stopped once the signal is received by the receiver. The sensor, then gives a signal on the output pin. The duration of the signal on the output pin will correspond to the time taken by the ultrasonic sound wave to travel to and from the sensor. We can read this duration and calculate the distance of the obstacle



Programming the HC-SR04

We are going to use only 1 pin of the microcontroller for both Trig / Echo of the HC-SR04. So far, we saw that we used the pinMode() function in the setup() function. However, we can use the same pinMode() function in the loop function and change a pin from Input to Output at run-time.  The Sensor Interface on the Induino R3 Board uses the Pin A2  (or Digital 16 ) The HC-SR04 outputs a digital signal and so we will use the digital pin number in our program. Place the HC-SR04 as in the image below





Finally we need a new function

pulseIn(pin number, signal type) => the PulseIn() function returns the duration for which the given pin is either HIGH or LOW(HIGH or LOW can be the signal type parameters passed to the function) The duration is returned in microseconds. 

Here's the program (this is the same as the PING Sensor program under examples)

/* Induino R3 User Guide - Program 6.3 - HC-SR04 Ultrasonic Sensor Interface*/

int pingPin = 16; // A2 - Connected to both Trig & ECHO Pins of the Sensor

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
}

void loop()
{
  // establish variables for duration of the ping, 
  // and the distance result in inches and centimeters:
  long duration, inches, cm;

  // The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH); // Sending a High Pulse on the Trig Pin of the Sensor
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);

  // The same pin is used to read the signal from the PING))): a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(pingPin, INPUT);
  duration = pulseIn(pingPin, HIGH);

  // convert the time into a distance
  inches = microsecondsToInches(duration);
  cm = microsecondsToCentimeters(duration);
  
  Serial.print(inches);
  Serial.print("in, ");
  Serial.print(cm);
  Serial.print("cm");
  Serial.println();
  
  delay(100);
}

long microsecondsToInches(long microseconds)
{
  // According to Parallax's datasheet for the PING))), there are
  // 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
  // second).  This gives the distance travelled by the ping, outbound
  // and return, so we divide by 2 to get the distance of the obstacle.
  // See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
  return microseconds / 74 / 2;
}

long microsecondsToCentimeters(long microseconds)
{
  // The speed of sound is 340 m/s or 29 microseconds per centimeter.
  // The ping travels out and back, so to find the distance of the
  // object we take half of the distance travelled.
  return microseconds / 29 / 2;
}



Thats It For This Part! Enjoy... and feel free to drop us an email with questions you might have -> info@simplelabs.co.in

Visit www.simplelabs.co.in for more interesting products

Back to List of Contents