Search code examples
embeddedi2catmegaservoadafruit

I2C with Atmega168


I'm trying to control several servos using the adafruit PWM servo controller. It uses i2c interface to communicate from the micro controller. https://www.adafruit.com/product/815

I'm using an Atmega 168 to attempt to send i2c instructions to the micro controller using a simple i2c library.

#include "i2c.h"

void initI2C(void) {
  TWBR = 32;                               /* set bit rate, see p. 242 */
                                     /* 8MHz / (16+2*TWBR*1) ~= 100kHz */
  TWCR |= (1 << TWEN);                                       /* enable */
}

void i2cWaitForComplete(void) {
  loop_until_bit_is_set(TWCR, TWINT);
}

void i2cStart(void) {
  TWCR = (_BV(TWINT) | _BV(TWEN) | _BV(TWSTA));
  i2cWaitForComplete();
}

void i2cStop(void) {
  TWCR = (_BV(TWINT) | _BV(TWEN) | _BV(TWSTO));
}

uint8_t i2cReadAck(void) {
  TWCR = (_BV(TWINT) | _BV(TWEN) | _BV(TWEA));
  i2cWaitForComplete();
  return (TWDR);
}

uint8_t i2cReadNoAck(void) {
  TWCR = (_BV(TWINT) | _BV(TWEN));
  i2cWaitForComplete();
  return (TWDR);
}

void i2cSend(uint16_t data) {
  TWDR = data;
  TWCR = (_BV(TWINT) | _BV(TWEN));                  /* init and enable */
  i2cWaitForComplete();
}

I found the addresses of the servo controller from the Arduino driver but I'm having issues setting the PWM of the board. Here is the code I'm attempting to use:

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "i2c.h"

#define SERVO_MIN 1000
#define SERVO_MAX 2000
#define SERVO_MID 1500

#define PCA9685_ADDR 0x4

#define PCA9685_MODE1 0x0

#define LED0_ON_L 0x6
#define LED0_ON_H 0x7
#define LED0_OFF_L 0x8
#define LED0_OFF_H 0x9

int main(void)
{
  initI2C();
  setupController();
  for(int i = 1; i < 17; i++) {
    setServo(i, 0, 4026);
  }
  return 0;
}

void setupController() {
    i2cStart();
    i2cSend(PCA9685_ADDR);
    i2cSend(PCA9685_MODE1);
    i2cSend(0x0);
    i2cStop();
}

void setServo(uint8_t id, uint16_t start, uint16_t stop) {
    i2cStart();
    i2cSend(PCA9685_ADDR);
    i2cSend(LED0_ON_L+4*id);
    i2cSend(start);
    i2cSend(start>>8);
    i2cSend(stop);
    i2cSend(stop>>8);
    i2cStop();
}

Here is the driver: https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library

I'm pretty sure my i2c isn't set up correctly? Any suggestions?

Thank you! :)


Solution

  • Your i2c library is wrong for the atmega168. The TWI data register is an 8bit register and you attempt to write a 16bit value into it. The issue in I2C Not working with PCA9685 uses an 8bit data TWI(i2c) driver.

    The i2c is initialized properly, due to it is per default powered and clocked at the reset of the atmega168, you don't need to care. But you should better check PRR.PRTWI register, if the TWI peripheral is powered or not - maybe you use a low power library that turns the TWI off.

    Furthermore, you are not explicitly ensuring for the wait time between two bytes on the bus, as you can see here: Address and 1 Byte Transfer on TWI After the ACK of the Slave and the next data written on the bus, there needs to be an idle window.

    So basically, you miss two major things:

    1. 8Bit data register needs to be written with 1Byte of data not with a unit16
    2. Explicit idle time between two bytes on the bus driven by master (you)