Showing posts with label Workaround. Show all posts
Showing posts with label Workaround. Show all posts

Wednesday, July 3, 2013

Accessing 24LC512 EEPROM: TWI/I2C emulation

Theory
Please refer to official 24LC512 specification and appropriate article at the Wikipedia for detailed information on 24LC512 EEPROM and theoretical vision of TWI/I2C protocol:






Practice
The following connection scheme and code is intended to be used with Atmel AT90USB162 MCU. First of all, we need to choose which MCU pins will be our SCL and SDA lines, I have chosen PB1 (pin 15) for SCL and PB2 (pin 16) for SDA. Refer to the Fritzing circuit in the previous post for connection scheme.

Define ports:
#define SCLPORT  PORTB
#define SCLDDR  DDRB

#define SDAPORT  PORTB
#define SDADDR  DDRB

#define SDAPIN  PINB
#define SCLPIN  PINB

#define SCL  PB1
#define SDA  PB2
SCL and SDA signal edges:
#define TWI_SDA_LOW SDADDR |= (1 << SDA)
#define TWI_SDA_HIGH SDADDR &= ~(1 << SDA)

#define TWI_SCL_LOW SCLDDR |= (1 << SCL)
#define TWI_SCL_HIGH SCLDDR &= ~(1 << SCL)
24LC512 commands, ACK/NACK (acknowledge) and delays definitions:
#define _24LC512_WRITE  0b10100000
#define _24LC512_READ   0b10100001

#define ACK_ACK         1
#define ACK_NACK        0

#define Q_DEL           _delay_loop_2(3);
#define H_DEL           _delay_loop_2(5);
TWI/I2C commands (Init/Start/Stop/Write/Read):
void TWI_Init (void) {
    SDAPORT &= ~(1 << SDA);
    SCLPORT &= ~(1 << SCL);
    TWI_SDA_HIGH;   
    TWI_SCL_HIGH;
}

void TWI_Start (void) {
    TWI_SCL_HIGH;
    H_DEL;
    TWI_SDA_LOW;
    H_DEL;
}

void TWI_Stop (void) {
    TWI_SDA_LOW;
    H_DEL;
    TWI_SCL_HIGH;
    Q_DEL;
    TWI_SDA_HIGH;
    H_DEL;
}

uint8_t TWI_Write (uint8_t Data) {
    uint8_t i;
    uint8_t ACK;
    for (i = 0; i < 8; i++) {
        TWI_SCL_LOW;
        Q_DEL;
        if (Data & 0x80) {
            TWI_SDA_HIGH;
        }
        else {
            TWI_SDA_LOW;
        }
        H_DEL;
        TWI_SCL_HIGH;
        H_DEL;
        while ((SCLPIN & (1 << SCL)) == 0);
        Data = Data << 1;
    }
    TWI_SCL_LOW;
    Q_DEL;
    TWI_SDA_HIGH;
    H_DEL;  
    TWI_SCL_HIGH;
    H_DEL;
    ACK = !(SDAPIN & (1 << SDA));
    TWI_SCL_LOW;
    H_DEL;
    return ACK;
}

uint8_t TWI_Read (uint8_t ACK) {
    uint8_t Data = 0x00;
    uint8_t i;
    for (i = 0; i < 8; i++) {
        TWI_SCL_LOW;
        H_DEL;
        TWI_SCL_HIGH;
        H_DEL;  
        while ((SCLPIN & (1 << SCL)) == 0);
        if (SDAPIN & (1 << SDA)) {
            Data |= (0x80 >> i);
        }
    }
    TWI_SCL_LOW;
    Q_DEL;
    if (ACK) {
        TWI_SDA_LOW;
    }
    else {
        TWI_SDA_HIGH;
    }
    H_DEL;
    TWI_SCL_HIGH;
    H_DEL;
    TWI_SCL_LOW;
    H_DEL;
    TWI_SDA_HIGH;
    return Data;
}
Finally, functions for accessing 24LC512 EEPROM:
uint8_t _24LC512_WriteByte (uint16_t Address, uint8_t Data) {
    TWI_Start();
    if (!TWI_Write(_24LC512_WRITE)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address >> 8)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Data)) {
        TWI_Stop();
        return FALSE;
    }
    TWI_Stop();
    _delay_ms(5);
    return TRUE;
}

uint8_t _24LC512_WritePage (uint16_t Address, uint8_t* Page, uint8_t Count) {
    TWI_Start();
    if (!TWI_Write(_24LC512_WRITE)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address >> 8)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address)) {
        TWI_Stop();
        return FALSE;
    }
    while (Count--) {
        if (!TWI_Write(*Page++)) {
            TWI_Stop();
            return FALSE;
        }
    }
    TWI_Stop();
    _delay_ms(5);
    return TRUE;
}

uint8_t _24LC512_ReadByte (uint16_t Address, uint8_t* Data) {
    TWI_Start();
    if (!TWI_Write(_24LC512_WRITE)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address >> 8)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address)) {
        TWI_Stop();
        return FALSE;
    }
    TWI_Start();
    if (!TWI_Write(_24LC512_READ)) {
        TWI_Stop();
        return FALSE;
    }
    *Data = TWI_Read(ACK_NACK);
    TWI_Stop();
    return TRUE;
}

uint8_t _24LC512_ReadPage (uint16_t Address, uint8_t* Page, uint8_t Count) {
    TWI_Start();
    if (!TWI_Write(_24LC512_WRITE)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address >> 8)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address)) {
        TWI_Stop();
        return FALSE;
    }
    TWI_Start();
    if (!TWI_Write(_24LC512_READ)) {
        TWI_Stop();
        return FALSE;
    }
    Count--;
    while (Count--) {
        *Page++ = TWI_Read(ACK_ACK);
    }
    *Page++ = TWI_Read(ACK_NACK);
    TWI_Stop();
    return TRUE;
}
That's it, very simple.
Download full library sources:

Sunday, September 16, 2012

Connecting URM04 v2.0 ultrasonic sensor to Arduino

If you have bought URM04 v2.0 ultrasonic sensor but forgot to buy IO expansion shield (which is required to connect it to your Adruino), you still may use simple workaround to achieve your goal.
All you need is a few wires and RS485 chip - MAX485 or its analogue (I have used ST485):
Wire up your Arduino, RS485 and URM04 as it shown at the picture:
If ultrasonic sensor is connected properly then its LED will blink four times after powering on (also it blinks every time the data is transmitted).
Prepare sketch in Arduino IDE:
// Pin number to enable XBee expansion board V5
int EN = 2;
//
// Measure distance using the URM04V2 ultrasonic sensor
void measureDistance(byte device) {
  digitalWrite(EN, HIGH);
  // Trigger distance measurement
  uint8_t DScmd[6] = {0x55, 0xaa, device, 0x00, 0x01, 0x00};   
  for(int i = 0; i < 6; i++) {
    Serial1.write(DScmd[i]);
    DScmd[5] += DScmd[i];
  }
  delay(30);
  // Send command to read measured distance 
  uint8_t STcmd[6] = {0x55, 0xaa, device, 0x00, 0x02, 0x00};
  for(int i = 0; i < 6; i++) {
    Serial1.write(STcmd[i]);
    STcmd[5] += STcmd[i];
  }
  delay(3);
}
//
// Return last measured distance by the URM04V2 ultrasonic sensor
// -1 means the last measurement is out of range or unsuccessful
int readDistance() {
  uint8_t data[8];
  digitalWrite(EN, LOW);
  boolean done = false;
  int counter = 0;
  int result = -1;
  while(!done) {
    int bytes = Serial1.available();
    if(bytes == 8) {
      for(int i = 0; i < 8; i++) {
        data[i] = Serial1.read();
      }
      result = (int)data[5] * 256 + (int)data[6];
      done = true;
    }
    else {
      delay(10);
      counter++;
      // If failed to read measured data for 5 times, give up and return -1
      if(counter == 5) {
        done = true;
      }
    }
  }
  return result;
}
//
void setup() {
  pinMode(EN, OUTPUT);
  delay(250);
  Serial.begin(19200);
  delay(250);
  Serial1.begin(19200);
  delay(250);
  digitalWrite(EN, HIGH);
  delay(1000);
}
//
void loop() {
  measureDistance(0x11);
  int distance = readDistance();
  Serial.println(distance);
  delay(1000);
}
Upload it to your board and open Serial Monitor to see the result - distance to the closest object in centimeters.
Important Note: I have Arduino Mega, so I'm using serial port 1 (Serial1 - RX1/TX1) to connect to URM, if you have Arduino UNO or another board model which has only one serial port (Serial - RX0/TX0), then you should connect URM to RX0/TX0 appropriately and replace Serial1.-commands with Serial.-commands in the sketch. Also in that case you will have to unplug wires from RX0/TX0 before uploading your code to the board because this port is also used by Arduino to connect with PC and that's why it's okay if you will see strange "Ua" character in Serial Monitor - just ignore it.