PIC tutorial to Arduino

I am trying to get the example from a tutorial at http://www.robot-electronics.co.uk/htm/using_the_i2c_bus.htm to work on an Arduino. I’m not fluent (yet) with my Arduino, and I’m a blank slate when it comes to PIC.

Somehow, the first 4 lines from the example are used to hook into the I2C bus - but I’m confounded how. Any assistance translating to the Arduino platform would be greatly appreciated.

Thanks,
Scott.

#define SCL     TRISB4 // I2C bus
#define SDA     TRISB1 //
#define SCL_IN  RB4    //
#define SDA_IN  RB1    // 

SDA = SCL = 1; 
SCL_IN = SDA_IN = 0;

void i2c_dly(void)
{
   // a slight delay
}

// The following 4 functions provide the primitive 
// start, stop, read and write sequences. All I2C 
// transactions can be built up from these.
void i2c_start(void)
{
  SDA = 1;             // i2c start bit sequence
  i2c_dly();
  SCL = 1;
  i2c_dly();
  SDA = 0;
  i2c_dly();
  SCL = 0;
  i2c_dly();
}

void i2c_stop(void)
{
  SDA = 0;             // i2c stop bit sequence
  i2c_dly();
  SCL = 1;
  i2c_dly();
  SDA = 1;
  i2c_dly();
}

unsigned char i2c_rx(char ack)
{
char x, d=0;
  SDA = 1; 
  for(x=0; x<8; x++) {
    d <<= 1;
    do {
      SCL = 1;
    }
    while(SCL_IN==0);    // wait for any SCL clock stretching
    i2c_dly();
    if(SDA_IN) d |= 1;
    SCL = 0;
  } 
  if(ack) SDA = 0;
  else SDA = 1;
  SCL = 1;
  i2c_dly();             // send (N)ACK bit
  SCL = 0;
  SDA = 1;
  return d;
}

bit i2c_tx(unsigned char d)
{
char x;
static bit b;
  for(x=8; x; x--) {
    if(d&0x80) SDA = 1;
    else SDA = 0;
    SCL = 1;
    d <<= 1;
    SCL = 0;
  }
  SDA = 1;
  SCL = 1;
  i2c_dly();
  b = SDA_IN;          // possible ACK bit
  SCL = 0;
  return b;
}

You are about to learn why the Atmel is superior to the PIC. (: This appears to be bit-banged I2C. The Atmel has I2C hardware built in, and the Wiring library has it wrapped up all nice and neat, so it’s a lot less painful than bit-banging.

Look at the I2C examples on the arduino playground and the wiring site.

FYI, Philips owns the name I2C, so you’ll find the same interface referred to as TWI (two wire interface) in Atmel documentation and other places.

Tell us what you’re interfacing, and maybe someone has already done it. For example, I have working code for the thermal array sensor sold on the website you reference, and even have some code to change the address so you can use more than one at a time.

-j

You are about to learn why the Atmel is superior to the PIC. (: This appears to be bit-banged I2C. The Atmel has I2C hardware built in, and the Wiring library has it wrapped up all nice and neat, so it’s a lot less painful than bit-banging.

Modern pics have I2C hardware built in too.

The problem with pics is that the F84 chip became popular so early (one of the first flash-based MCUs, hobbiests loved it). That chip has no peripheral support, but since the hobbiests used it, a lot of the code available on the web does everything in software.

The PIC can do this properly, it’s the users that are at issue here.

The PIC can do this properly, it's the users that are at issue here.

Yep, users and tools. Is there a free compiler available for the PIC yet?

Not that I really care, I'll be sticking with the AVRs and gcc.

-j

What I'm trying to do is use an Arduino (as slave) to echo the commands it receives from a master to the serial port so I can "see" what exactly it is asking for. My serial window would (hopefully) look something like:

... rec'd: start ... rec'd: read request, address 0x00 ... reply: data 0xFF (address 0x00) ... rec'd: ack ... reply: data 0x73 (address 0x01) ... rec'd: nack ... rec'd: end ... waiting... ... waiting...

So, as you can see, the Wire library (although a wonderful thing) is a bit too abstracted from the level of granularity I am looking for. Hopefully, I'll be able to use the code from the tutorial to "bit-bang" the bus and serially keep the human (me) appraised of what's going on.

I have (I think) learned the following:

In PICspeak:

#define SCL TRISB4 // I2C bus
#define SDA TRISB1 //
#define SCL_IN RB4 //
#define SDA_IN RB1 //

is the equivalent of

#define SCL DDRC5 // ← Does using ‘DDRC5’ work
on an Arduino (addressing Analog Pin5) ?
#define SDA DDRC4 //
#define SCL_IN PINC5 // ← does this as well?
#define SDA_IN PINC4 //

and the line

SDA = SCL = 1;

is the equivalent of setting the pins to INPUT with a pinMode(pin, INPUT) command.

This:

SCL_IN = SDA_IN = 0;

is setting both pins LOW

So are terms like DDRC5, PORTC5 and PINC5 legal commands on the Arduino? I can find nothing in the reference that does so, but that doesn’t mean it won’t work.

So are terms like DDRC5, PORTC5 and PINC5 legal commands on the Arduino?

I know you can use DDRx and PORTx to get at the whole port (8 bits at a time). Not so sure about the bit-level access, though. Maybe someone else knows.

-j

Here is an example of how you can access the ports directly:

#define I2CPORT PORTC // this is the port the arduino uses for analog
#define I2CDDR DDRC // this is the data direction reg for the above
#define SCL 5 // this is pin 5 of the port
#define SDA 4 // pin 4 of the port

// code fragments for direct port access
I2CDDR |= 1 << SCL; // set Data Direction for SCL pin as output (set DDR pin high)
I2CPORT |= 1 << SCL ; // set the SCL pin high
I2CPORT &= ~(1 << SCL) ; // set the SCL pin low

I2CDDR &= ~(1 << SCL); // set DDR for SCL pin as input (take DDR pin low)
if(bit_is_set(I2CPORT,SDA ))
; // do something if SDA pin is high

if( bit_is_clear(I2CPORT,SDA ))
; // do something if SDA is low

Arduino port mapping info is here:
http://www.arduino.cc/en/Reference/PortManipulation

Many thanks... I will give it a go and we'll how long it takes me to get stuck (again)!

Hi scott,

I hope this wont confuse you but you are welcome to use the macro below to do the low level writes to the port. It does the same thing as the previous post but is more consistant with arduino syntax

// this macro does fast digital write to the pins mapped to the analog port
#define fastWriteA(pin,state) (state ? PORTC |= 1 << (pin -14) : PORTC &= ~(1 << (pin -14) ))

Example:
#define SDA 18 // this is the Arduino digital pin number for using the analog pin 4 as digital ports
#define SCL 19 // Arduino digital pin 19 is analog pin 5

fastWrite(SCL,HIGH); // set clock pin high
fastWrite(SDA,LOW); // set data pin high

Note that the macro does the same things as digitalWrite(SDA, HIGH); but is over 30 times faster than using the arduino function. But if your code is not speed critical then it may be easier just to use the Arduino digitalWrite functions.

Here’s a more direct translation of the PIC code into the arduino environment. It hasn’t been tested, or even compiled, but it should give you an idea of the way things are done. Note that this is not a particularly “efficient” method of implementing I2C on arduino; it’s using Arduino primitives (digitalRead, etc) instead of direct port access, for instance. But the result is (hopefully) somewhat more understandable.

The code is made more difficult to understand because they have set up the I2C bus as an “open collector” bus; this allows it to be shared between multiple devices. But it means that each device can only write “zero” to the bus. To create a ONE state on the bus, the pin is turned into an input, and the external resistor you see on the OP-referenced web page “pulls up” the bus to the one state.) This means that instead of outputting ones and zeros (say, using digitalWrite), the code outputs a permanent zero and switches the pin from input (allows bus to go to 1 state) and output (zero goes out and pulls the bus low.) On the PIC, the “TRISxx” register bits control the direction of the corosponding data register bits. For example:

  #define SCL     TRISB4 // direction of pin B4, used as SCL
  #define SCL_IN  RB4    //  Actual port pin B4 (SCL)

In the arduino environment, we’ll use pinMode(), which is less obvious (the PIC code NAMES the direction register as if they were data bits, and the assignments happen to work out the same…) I’m not ENTIRELY sure that the pinMode functions, or the AVR pins, work exactly like this. I looked BRIEFLY, and I think it should be OK, but…) Here’s what I came up with:

#define SCL_PIN  4    // Arduino pin number for SCL_IN
#define SDA_PIN  1    //  Arduino pin number for SDA_IN

pinmode(SCL_PIN, INPUT);   // Pins are inputs (floating) to allow
pinmode(SDA_PIN, INPUT);   //   other users on the bus.
digitalWrite(SCL_PIN, 0);  // If they become outputs, we want a low
digitalWrite(SDA_PIN, 0);  //   level to be output.

void i2c_dly(void)
{
   // a slight delay
}

// The following 4 functions provide the primitive 
// start, stop, read and write sequences. All I2C 
// transactions can be built up from these.
void i2c_start(void)
{
  pinMode(SDA_PIN, INPUT);             // i2c start bit sequence
  i2c_dly();
  pinMode(SCL_PIN, INPUT);      // SDA, SCL == 1;
  i2c_dly();
  pinMode(SDA_PIN, OUTPUT);      // Setting output causes 0
  i2c_dly();
  pinMode(SCL_PIN, OUTPUT);
  i2c_dly();
}

void i2c_stop(void)
{
  pinMode(SDA_PIN, OUTPUT);             // i2c stop bit sequence
  i2c_dly();                           // output 0
  pinMode(SCL_PIN, INPUT);
  i2c_dly();
  pinMode(SDA_PIN, INPUT);
  i2c_dly();
}

unsigned char i2c_rx(char ack)
{
char x, d=0;
  pinMode(SDA_PIN, INPUT);            // = 1
  for(x=0; x<8; x++) {
    d <<= 1;
    do {
      pinMode(SCL_PIN, INPUT);            // = 1
    }
    while(digitalRead(SCL_PIN)==0);    // wait for any SCL clock stretching
    i2c_dly();
    if(digitalRead(SDA_PIN)) d |= 1;
    pinMode(SCL_PIN, OUTPUT);         // = 0
  } 
  if(ack) pinMode(SDA_PIN, OUTPUT);   // = 0
  else pinMode(SDA_PIN, INPUT);       // = 1
  pinMode(SCL_PIN, INPUT);            // = 1
  i2c_dly();             // send (N)ACK bit
  pinMode(SCL_PIN, OUTPUT);   // = 0
  pinMode(SDA_PIN, INPUT);    // = 1
  return d;
}

bit i2c_tx(unsigned char d)
{
char x;
static bit b;
  for(x=8; x; x--) {
      if(d&0x80) pinMode(SDA_PIN, INPUT);  // = 1
      else pinMode(SDA_PIN, OUTPUT);   // = 0
      pinMode(SCL_PIN, INPUT);         // = 1
    d <<= 1;
    pinMode(SCL_PIN, OUTPUT);          // = 0
  }
  pinMode(SDA_PIN, INPUT);   // = 1
  pinMode(SCL_PIN, INPUT);   // = 1
  i2c_dly();
  b = digitalRead(SDA_PIN);          // possible ACK bit
  pinMode(SCL_PIN, OUTPUT);   // = 0
  return b;
}

Amazing! Thanks a lot. One question: You have

#define SCL_PIN  4    // Arduino pin number for SCL_IN
#define SDA_PIN  1    //  Arduino pin number for SDA_IN

I was under the impression that SCL was on Analog Pin 5 (which is really digital pin 19) and SDA was Analog Pin 4 (digital 18) and not on pin 1. Which makes me think it should be:

#define SCL_PIN  19    // Arduino pin number for SCL_IN
#define SDA_PIN  18   //  Arduino pin number for SDA_IN

Which one is correct?

the code I posted, like the original PIC code, does "bit banged" I2C (every twitch of every bit and pin involved is handled by software.) This means:

1) Any two digital IO pins can be used for SCL and SDA; you're not restricted to using the labeled pins, which are for use with the built-in hardware support.

2) The resulting code is slower and uses more CPU cycles than code that uses the built-in hardware.

If you CAN use the HW support, you should probably stick with the Wiring library for using it.