Understanding Code

Hey Guys
Newbie!

I am beginning a project to have my uno control an aquarium. I plan on doing this by learning each component and writing code to drive that compnent. Then hopefully by the end i would of learnt enough to tie it all together. The first part of this is using an sainsmart I2C LCD and a sparkfun ds1307 clock.

I have found example code for each device and i am trying to understand the code in its entirety. I have some questions on the RTC code that is tied to this code, but i will start with this one!

I think i understand most of this code, apart from the bit starting with define and the line

" LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin)"

So its a standard screen with the sainsmart i2c adaptor on the back. i have a non i2c version of this screen and have got that working no problem, what is the define part of this code exactly doing. Is it simply renaming pin numbers with a name? I understand the above line is defining what pins are used on the lcd. the same as if the screen was connected directly to the arduino without the i2c adaptor. Infact i cant see much difference between the code connecting the lcd directly to the uno with all the wires and using the i2c.

/*
** Example Arduino sketch for SainSmart I2C LCD2004 adapter for HD44780 LCD screens
** Readily found on eBay or http://www.sainsmart.com/ 
** The LCD2004 module appears to be identical to one marketed by YwRobot
**
** Edward Comer
** LICENSE: GNU General Public License, version 3 (GPL-3.0)
**
** sain_lcd_2.ino
** Simplified and modified by Andrew Scott for Arudino 1.0.1, Arudino Uno R3.
** NOTE: I2C pins are A4 SDA, A5 SCL
** Don't forget to use the new LiquidCrystal Library.
*/



#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>

#define I2C_ADDR    0x3F  // Define I2C Address where the SainSmart LCD is
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7

LiquidCrystal_I2C	lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

void setup()
{
  lcd.begin (20,4);
  
  // Switch on the backlight
  lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
  lcd.setBacklight(HIGH);

  // Position cursor and write some text
  lcd.home ();                   // go to the first line, first character
  lcd.print("SainSmart I2C tester");  
  lcd.setCursor ( 0, 1 );        // go to the 2nd line
  lcd.print("It's Working!");
  lcd.setCursor ( 0, 2 );        // go to the third line
  lcd.print("Sainsmarts docs suck");
  lcd.setCursor ( 0, 3 );        // go to the fourth line
  lcd.print("Nice LCD Though. ");
}

void loop()
{

}

I have been researching I2C and in particular the RTC mentioned above. an example code looks like this:

#include <Wire.h>

int hour;
int minute;
int second;
int month;
int day_of_week;
int day;
int year;

char* dow[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

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

void loop()
{
  // Below required to reset the register address to 0.
  Wire.beginTransmission(104); // transmit to device #104, the ds 1307
  Wire.send(0x00);
  Wire.endTransmission();    // stop transmitting
 
  Wire.requestFrom(104, 7);    // request 7 bytes from slave ds1307, we'll assume it'll send them all even though it doesn't have to
  second = Wire.receive(); 
  minute = Wire.receive(); 
  hour = Wire.receive(); 
  day_of_week=Wire.receive(); 
  day = Wire.receive(); 
  month = Wire.receive(); 
  year = Wire.receive(); 

  // Convert all the BCD values that might have "tens" to decimal.  Most arduino folks do this w/shifts but this just looks easier to me.
  hour=hour/16 * 10 + hour % 16;
  minute=minute/16 * 10 + minute % 16;
  second=second/16 * 10 + second % 16;
  day=day/16 * 10 + day % 16;
  month=month/16 * 10 + month % 16;
  year=2000 + year/16 * 10 + year % 16;
  
  Serial.print(hour);
  Serial.print(":");
  if (minute < 10) { Serial.print("0"); }
  Serial.print(minute);
  Serial.print(":");
  if (second < 10) { Serial.print("0"); }
  Serial.print(second);
  Serial.print(" ");
  Serial.print(dow[day_of_week-1]);  // array is 0-6, but the dow register holds 1-7, so subtract 1.
  Serial.print(", ");
  Serial.print(month);
  Serial.print("/");
  Serial.print(day);
  Serial.print("/");
  Serial.print(year);
  Serial.print("\n");
  delay(1000);
}

i again think i understand most of this code, except the first part of the loop upto the part endtransmission. Why do we not use wire.write for the LCD screen?. its seems we use alot of code to communicate with the clock, ie begintranmission etc, but why do we not use such commands with the lcd?

Kr
Craig

what is the define part of this code exactly doing

It's telling the library which pins on the I2C I/O expander are connected to which pins on the LCD.

Why do we not use wire.write for the LCD screen?

The wire commands that you use for the RTC are in the liquid crystal library(s). When you do a lcd.print() the library code addresses the lcd, transmits the data and ends the transmission.

again think i understand most of this code, except the first part of the loop up to the part endtransmission.

This is how the the Wire library selects a data register. The code says to start at register 0 and to read the data from the next seven registers.

If you take a look at the data sheet for the DS1307 you can see how the time and data is packed into the storage registers

Thanks for the repies

i really wanna get my head round this so i dont have to copy code, and hopefully i will be able to write my own code!

This is how the the Wire library selects a data register. The code says to start at register 0 and to read the data from the next seven registers.

If you take a look at the data sheet for the DS1307 you can see how the time and data is packed into the storage registers

So if i am reading the data sheet right, it says send a 0 to read and a 1 to write before you go on to the next byte. This is sent after the address is sent. Am i right to assume the wire.read and wire.write commands take care of this!

The wire commands that you use for the RTC are in the liquid crystal library(s). When you do a lcd.print() the library code addresses the lcd, transmits the data and ends the transmission.

So is it correct to say you dont need the wire library if the lcd i2c library takes care of these commands.

It's telling the library which pins on the I2C I/O expander are connected to which pins on the LCD.

Ok that makes perfect sense. I understand some people have had issues with the sainsmart i2c expander. And as such have had to try different libraries. So if i download a library, then that library has instructions / code that i put in to my code. When i call upon that instruction, the library then performs a task or function. So in order to write a code, you need to know what commands to insert in the code to get that library to carry out the function. For example wire.write, lcd.begin etc. So how do you find out what these are for a given library?

Kr
craig

So is it correct to say you dont need the wire library if the lcd i2c library takes care of these commands.

You still need the Wire library as the liquid crystal library uses it (calls wire functions). You still need to include the Wire library. Try not including it to see. To see what the functions and required function parameters are for a library, look at the header (.h) file. All member functions for that library (class) are declared there. The .cpp file has the member function definitions, ie the guts of the functions.

So if i am reading the data sheet right, it says send a 0 to read and a 1 to write before you go on to the next byte. This is sent after the address is sent.

No it is sent with the address as the least significant bit.

Am i right to assume the wire.read and wire.write commands take care of this!

Yes this command shifts the address bits one place to the left and adds 0 or 1 as appropriate but it is all sent out as one byte.

So how do you find out what these are for a given library?

It should be in the documentation of the library, if not look at the code of the library. Ultimately the numbers you need to send are in the data sheet of the device.

No it is sent with the address as the least significant bit.

  Wire.beginTransmission(104); // transmit to device #104, the ds 1307
  Wire.send(0x00);
  Wire.endTransmission();    // stop transmitting

so the first line, wire.beginTransmission(104) tell the arduino to open a channel to device 104. Where is this zero?
wire.send(0x00) i know 0x00 is hex for zero. so as said, this is telling the device which register is required.

So we have libraries using libraries. and anyone can write a library in any format. So i guess then there is no fixed standard of code or commands? they can all be different!

Kr
Craig

so the first line, wire.beginTransmission(104) tell the arduino to open a channel to device 104. Where is this zero?

It is in the least significant bit.
104 is hex 68
What is sent on a read is 0xD1 which is 0x68 shifted up by one place plus a 1 in the least significant bit. On a write it sends 0xD0, with a zero in the least significant bit. The least significant bit is the read / write bit.

So we have libraries using libraries.

Yes what is wrong with that?

So i guess then there is no fixed standard of code or commands?

Not sure what you mean. The commands are in the data sheet for the chip.

wire.send(0x00) i know 0x00 is hex for zero. so as said, this is telling the device which register is required.

If you download the PDF spec sheet for the RTC, there is a table that explains the time keeper registers, which are all in BCD format. Each time you read a time keeper register, an internal pointer is incremented to point to the next "byte-wide" register. After the last reister is read, the pointer "wraps around" to point to the first register (i.e., register 0). The statement:

 Wire.send(0x00);

forces the register pointer to reset to register 0. Then you can read the 7 registers in order.

I've attached some standard BCD encode/decode routines that you can use instead of the inline conversions, if you want.

/*****
  This function converts the byte-length Binary Coded Decimal value returned from the RTC
  to a decimal number.
 
  Parameter list:
    byte value        A number expressed in BCD
    
  Return value:
    byte              The BCD value expressed as a decimal number
    
*****/
byte BcdToDec(byte value)
{
  return ((value / 16) * 10 + value % 16);  // Think about it...
}

/*****
  This function converts the byte-length decimal value to a Binary Coded Decimal value. 
 
  Parameter list:
    byte value        A number expressed as a decimal value
    
  Return value:
    byte              The decimal value expressed as a BCD number
    
*****/
byte DecToBcd(byte value){
  return (value / 10 * 16 + value % 10);  // Think about this one, too.
}

Thanks for the advice so far!

So the wire.beginTransmission(104) tells the system we want to talk to device 104 or 0x68 or 0110 1000. These 3 addresses are all the same !

oh hang on! in the RTC data sheet the address is 110 1000. Which is 7 bit!

then wire.send(0x00) then sends 0000 0000. so this tells the RTC we want to read from register 0. this is the register where our data is stored. If the data was stored in register 1, then we would wire.send(0x01) which is 0000 0001?
Then we end transmission!

so the last command the RTC received was that the following instruction will be concerning register 0

then we do wire.request(104, 7)
then so on.

It is in the least significant bit.
104 is hex 68
What is sent on a read is 0xD1 which is 0x68 shifted up by one place plus a 1 in the least significant bit. On a write it sends 0xD0, with a zero in the least significant bit. The least significant bit is the read / write bit.

Can you explain this to me again. so the LSB is the number to the most right. For eg 0001 0001. So the LSB is 1? so this is where i am getting confused. so the actual address is only 7 bit. so when we are sending 104 or 0x68 we are sending 8 bits. with the LSB being 0. so are we telling the device through requesting an address that we will be reading and not writing?

If you download the PDF spec sheet for the RTC, there is a table that explains the time keeper registers, which are all in BCD format. Each time you read a time keeper register, an internal pointer is incremented to point to the next "byte-wide" register. After the last reister is read, the pointer "wraps around" to point to the first register (i.e., register 0).

That makes pefect sense. so because we only want to read 7 bits of data as laid out in the data sheet, without this line then if we run the loop again, we will read register 8 to 15! and so on. I aint very good at reading data sheets being a newbie!

Kr
Craig

oh hang on! in the RTC data sheet the address is 110 1000. Which is 7 bit!

yes that is what you said:-

So the wire.beginTransmission(104) tells the system we want to talk to device 104 or 0x68 or 0110 1000. These 3 addresses are all the same !

They are three ways of representing the same bit pattern yes. Notice all of these could be expressed as a 7 bit number because the most significant bit is zero.

then wire.send(0x00) then sends 0000 0000. so this tells the RTC we want to read from register 0. this is the register where our data is stored. If the data was stored in register 1, then we would wire.send(0x01) which is 0000 0001?
Then we end transmission!

Yes that primes the device as to the internal register you want to read when you do the I2C read command.

so the LSB is the number to the most right

Yes.

so are we telling the device through requesting an address that we will be reading and not writing?

Yes.

without this line then if we run the loop again, we will read register 8 to 15! and so on.

Yes.