1

I'm studying the Wire library code driver utility/twi.h to learn how twi buffering and ISR management work.

What I did actually, is that I copied the header/source files from the Arduino path F:\Program Files\Arduino\hardware\arduino\avr\libraries\Wire\src\utility and put them in the Arduino custom libraries and changed the .c to .cpp and it worked.

I actually ran through different i2c libraries for avr and this code library is one of the best IMHO.

My test code is to initialize the HMC5883L, and read back those configuration setting from the first SFRs of the HMC5883L.

I'm using Serial.prints inside/outside the ISR to debug my coding. It is working pretty much OK. I'm not sure if this could cause some problems.

But, because I don't have a complete understanding of how twi works, I'm trying to understand things now as displaying the content that is going in the ISR.

I think the initialize is working ok, but the read procedure may not as it should be.

Here's my code, for application code in the Arduino IDE, and the code in the library.

Arduino IDE code:

#include "twi.h"    
////////////////////////////////////////////////////////////////////////////
// HMC5883L
#define HMC5883L                    0x1E
#define CONFIG_REG_A                0x00  //Read/Write
#define CONFIG_REG_B                0x01  //Read/Write
#define MODE_REGISTER               0x02  //Read/Write
#define DATA_OUTPUT_X_MSB_REGISTER  0x03  //Read
#define DATA_OUTPUT_X_LSB_REGISTER  0x04  //Read
#define DATA_OUTPUT_Z_MSB_REGISTER  0x05  //Read
#define DATA_OUTPUT_Z_LSB_REGISTER  0x06  //Read
#define DATA_OUTPUT_Y_MSB_REGISTER  0x07  //Read
#define DATA_OUTPUT_Y_LSB_REGISTER  0x08  //Read
#define STATUS_REGISTER             0x09  //Read
#define IDENTIFICATION_REGISTER_A   0x10  //Read
#define IDENTIFICATION_REGISTER_B   0x11  //Read
#define IDENTIFICATION_REGISTER_C   0x12  //Read
#define DECLINATION_ANGLE 3.46

void hmc5883l_init(void);
void init_read(void);
void hmc5883l_print_serial(void);

void setup() {
  Serial.begin(9600);
  twi_init();
  hmc5883l_init();
  init_read();
}

void loop() {
  hmc5883l_print_serial();
}

void hmc5883l_init(void){
  uint8_t err,HMC5883L_ini[] = {CONFIG_REG_A,0x78,0x20,0x00};//
    err = twi_writeTo(HMC5883L,HMC5883L_ini,4,1,1);
    if(err != 0){Serial.print("error of init is");Serial.println(err);}
    Serial.println();
}

void init_read(void){
    uint8_t err,len,dat[3];
    err = twi_writeTo(HMC5883L,0x00,1,0,1);
    if(err != 0){Serial.print("error of init is");Serial.println(err);}
    Serial.println();   

    len = twi_readFrom(HMC5883L,dat,3,1);
    Serial.print("length of read op:              ");
    Serial.println(len);
    Serial.println();        

    Serial.print("config settings:                ");
    Serial.print(dat[0],HEX);Serial.print("\t");
    Serial.print(dat[1],HEX);Serial.print("\t");
    Serial.println(dat[2],HEX);    
}

void hmc5883l_print_serial(void){
  int16_t x,z,y;
  uint8_t data[6],len,err, dst_reg[] = {0x03};
  float heading,heading_in_degrees,declination_angle_YANBU;

  err = twi_writeTo(HMC5883L,dst_reg,1,0,1);
  if(err != 0){Serial.print("error of init is");Serial.println(err);}
  Serial.println();

  len = twi_readFrom(HMC5883L,data,6,1);
  Serial.print("len is: ");Serial.println(len);
  x = data[0] << 8 | data[1];
  z = data[2] << 8 | data[3];
  y = data[4] << 8 | data[5];

  heading = atan2(y,x);
  declination_angle_YANBU = ((3.0 + (52.0 / 60.0)) / (180 / M_PI));  // in yanbu city it's 
  heading += declination_angle_YANBU;

  if (heading < 0){heading += 2 * PI;}
  if (heading > 2 * PI){heading -= 2 * PI;} 

  heading_in_degrees = heading * 180 / M_PI;
  Serial.println(heading_in_degrees);

  //Serial.print("x-axis\t");Serial.print("z-axis\t");Serial.println("y-axis");
  Serial.print(x);Serial.print("\t");
  Serial.print(z);Serial.print("\t");
  Serial.println(y);
}

And this is the core library:

In this part, I've put Serial.prints inside the ISR cases to display what's going from the start of the tx/rx buffers. Also, I've cut the slave half of the code because it's not necessary in this problem, as I'm only dealing with HMC5883L as a slave and I'm the master.

#include <math.h>
#include <stdlib.h>
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <compat/twi.h>
#include "Arduino.h" // for digitalWrite

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif

#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#include "pins_arduino.h"
#include "twi.h"

static volatile uint8_t twi_state;
static volatile uint8_t twi_slarw;
static volatile uint8_t twi_sendStop;           // should the transaction end with a stop
static volatile uint8_t twi_inRepStart;         // in the middle of a repeated start

static void (*twi_onSlaveTransmit)(void);
static void (*twi_onSlaveReceive)(uint8_t*, int);

static uint8_t twi_masterBuffer[TWI_BUFFER_LENGTH];
static volatile uint8_t twi_masterBufferIndex;
static volatile uint8_t twi_masterBufferLength;

static uint8_t twi_txBuffer[TWI_BUFFER_LENGTH];
static volatile uint8_t twi_txBufferIndex;
static volatile uint8_t twi_txBufferLength;

static uint8_t twi_rxBuffer[TWI_BUFFER_LENGTH];
static volatile uint8_t twi_rxBufferIndex;

static volatile uint8_t twi_error;

void twi_init(void)
{
  // initialize state
  twi_state = TWI_READY;
  twi_sendStop = true;      // default value
  twi_inRepStart = false;

  // activate internal pullups for twi.
  digitalWrite(SDA, 1);
  digitalWrite(SCL, 1);

  // initialize twi prescaler and bit rate
  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);
  TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;

  // enable twi module, acks, and twi interrupt
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
}

void twi_disable(void)
{
  // disable twi module, acks, and twi interrupt
  TWCR &= ~(_BV(TWEN) | _BV(TWIE) | _BV(TWEA));

  // deactivate internal pullups for twi.
  digitalWrite(SDA, 0);
  digitalWrite(SCL, 0);
}

uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sendStop)
{
  uint8_t i;

  // ensure data will fit into buffer
  if(TWI_BUFFER_LENGTH < length){
    return 0;
  }

  // wait until twi is ready, become master receiver
  while(TWI_READY != twi_state){
    continue;
  }
  twi_state = TWI_MRX;
  twi_sendStop = sendStop;
  // reset error state (0xFF.. no error occured)
  twi_error = 0xFF;

  // initialize buffer iteration vars
  twi_masterBufferIndex = 0;
  twi_masterBufferLength = length-1;  // This is not intuitive, read on...

  twi_slarw = TW_READ;
  twi_slarw |= address << 1;

  if (true == twi_inRepStart) {
    twi_inRepStart = false;         // remember, we're dealing with an ASYNC ISR
    do {
      TWDR = twi_slarw;
    } while(TWCR & _BV(TWWC));
    TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE);  // enable INTs, but not START
  }
  else
    // send start condition
    TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);

  // wait for read operation to complete
  while(TWI_MRX == twi_state){
    continue;
  }

  if (twi_masterBufferIndex < length)
    length = twi_masterBufferIndex;

  // copy twi buffer to data
  for(i = 0; i < length; ++i){
    data[i] = twi_masterBuffer[i];
  }

  return length;
}

uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait, uint8_t sendStop)
{
  uint8_t i;

  // ensure data will fit into buffer
  if(TWI_BUFFER_LENGTH < length){
    return 1;
  }

  // wait until twi is ready, become master transmitter
  while(TWI_READY != twi_state){
    continue;
  }
  twi_state = TWI_MTX;
  twi_sendStop = sendStop;
  // reset error state (0xFF.. no error occured)
  twi_error = 0xFF;

  // initialize buffer iteration vars
  twi_masterBufferIndex = 0;
  twi_masterBufferLength = length;

  // copy data to twi buffer
  for(i = 0; i < length; ++i){
    twi_masterBuffer[i] = data[i];
  }

  // build sla+w, slave device address + w bit
  twi_slarw = TW_WRITE;
  twi_slarw |= address << 1;

  // if we're in a repeated start, then we've already sent the START
  // in the ISR. Don't do it again.
  //
  if (true == twi_inRepStart) {
    // if we're in the repeated start state, then we've already sent the start,
    // (@@@ we hope), and the TWI statemachine is just waiting for the address byte.
    // We need to remove ourselves from the repeated start state before we enable interrupts,
    // since the ISR is ASYNC, and we could get confused if we hit the ISR before cleaning
    // up. Also, don't enable the START interrupt. There may be one pending from the 
    // repeated start that we sent outselves, and that would really confuse things.
    twi_inRepStart = false;         // remember, we're dealing with an ASYNC ISR
    do {
      TWDR = twi_slarw;             
    } while(TWCR & _BV(TWWC));
    TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE);  // enable INTs, but not START
  }
  else
    // send start condition
    TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE) | _BV(TWSTA); // enable INTs

  // wait for write operation to complete
  while(wait && (TWI_MTX == twi_state)){
    continue;
  }

  if (twi_error == 0xFF)
    return 0;   // success
  else if (twi_error == TW_MT_SLA_NACK)
    return 2;   // error: address send, nack received
  else if (twi_error == TW_MT_DATA_NACK)
    return 3;   // error: data send, nack received
  else
    return 4;   // other twi error
}

void twi_reply(uint8_t ack)
{
  if(ack){
    TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
  }else{
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
  }
}

void twi_stop(void)
{
  // send stop condition
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO);

  while(TWCR & _BV(TWSTO)){
    continue;
  }

  // update twi state
  twi_state = TWI_READY;
}

/* 
 * Function twi_releaseBus
 * Desc     releases bus control
 * Input    none
 * Output   none
 */
void twi_releaseBus(void)
{
  // release bus
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);

  // update twi state
  twi_state = TWI_READY;
}

ISR(TWI_vect)
{
  switch(TW_STATUS){
    // All Master
    case TW_START:     // sent start condition
    case TW_REP_START: // sent repeated start condition
      // copy device address and r/w bit to output register and ack
      TWDR = twi_slarw;
      Serial.print("mt add sent is:                 0x");
      Serial.println(twi_slarw, HEX);
      twi_reply(1);
      break;

    // Master Transmitter
    case TW_MT_SLA_ACK:  // slave receiver acked address
    case TW_MT_DATA_ACK: // slave receiver acked data
      // if there is data to send, send it, otherwise stop 
      if(twi_masterBufferIndex < twi_masterBufferLength){
        // copy data to output register and ack
        Serial.print("mt byte transmitted no:         ");
        Serial.println(twi_masterBufferIndex);
        Serial.print("content of tx byte:             0x");
        Serial.println(twi_masterBuffer[twi_masterBufferIndex], HEX);       
        TWDR = twi_masterBuffer[twi_masterBufferIndex++];
        twi_reply(1);
      }else{
    if (twi_sendStop){
        Serial.println("mt stop");
        twi_stop();
    }

    else {
      Serial.println("mt repst");
      twi_inRepStart = true;    // we're gonna send the START
      TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ;
      twi_state = TWI_READY;
    }
      }
      break;
    case TW_MT_SLA_NACK:  // address sent, nack received
    Serial.println("mt add sent nack");
      twi_error = TW_MT_SLA_NACK;
      twi_stop();
      break;
    case TW_MT_DATA_NACK: // data sent, nack received
      twi_error = TW_MT_DATA_NACK;
      twi_stop();
      break;
    case TW_MT_ARB_LOST: // lost bus arbitration
      twi_error = TW_MT_ARB_LOST;
      twi_releaseBus();
      break;

    // Master Receiver
    case TW_MR_DATA_ACK: // data received, ack sent
      // put byte into buffer
        Serial.print("mr byte received no:            ");
        Serial.println(twi_masterBufferIndex);      
        Serial.print("content of rx byte:             0x");
        Serial.println(twi_masterBuffer[twi_masterBufferIndex], HEX);       
      twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
    case TW_MR_SLA_ACK:  // address sent, ack received
      // ack if more bytes are expected, otherwise nack
      if(twi_masterBufferIndex < twi_masterBufferLength){
        twi_reply(1);
      }else{
        twi_reply(0);
      }
      break;
    case TW_MR_DATA_NACK: // data received, nack sent
      // put final byte into buffer
        Serial.print("mr last received byte:          ");
        Serial.println(twi_masterBufferIndex);  
        Serial.print("content of last rx byte:        0x");
        Serial.println(twi_masterBuffer[twi_masterBufferIndex], HEX);       
      twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
    if (twi_sendStop)
          twi_stop();
    else {
      twi_inRepStart = true;
      TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ;
      twi_state = TWI_READY;
    }    
    break;
    case TW_MR_SLA_NACK:
      twi_stop();
      break;
}

The problem now is:

I get these readings on the Arduino serial monitor. Note: I've wrote my notes on each section.

// initialization
// I think this process is done just fine, the exact bytes are sent
// but each time I open the serial monitor I get like garbage at the first 
// of the window, like this flipped question mark I get it most the time
// Is there something like flush function to clear things?
⸮mt add sent is:                 0x3C
mt byte transmitted no:         0
content of tx byte:             0x0
mt byte transmitted no:         1
content of tx byte:             0x78
mt byte transmitted no:         2
content of tx byte:             0x20
mt byte transmitted no:         3
content of tx byte:             0x0
mt stop


// writing target register which is 0x00, but I get 0xA3 ??!
m
mt byte transmitted no:         0
content of tx byte:             0xA3
mt stop

// Here requesting the 6 bytes data, I send read of HMC5883L which is 0x3D
// but 1st byte should be 0x78 then 2nd should be 0x20, 3rd 0x00
// but the result is different ! it's like they are shifted somehow

// At the end I'm trying to read back the passed array to the function, but 
// I get 0s!!
mt add sent is:                 0x3D
mr byte received no:            0
content of rx byte:             0xA3
mr byte received no:            1
content of rx byte:             0x78
mr last received byte:          2
content of last rx byte:        0x20
length of read op:              3

config settings:                0   0   0
  • 1
    That is not the core library, those lower level functions are part of the Wire library. – Jot Apr 28 at 16:23
  • Aaah, OK, I then didn't differentiate between core/lower functions. OK, so what could be like core library? Is Arduino.h and other related libraries that are within Arduino folder in installation folder, are the core libraries for Arduino ? – R1S8K Apr 28 at 16:33
  • 1
    There is no "core" library. There are a number of Wire libraries (official and other compatible libraries) for different processors. It just happens that the Wire library for the avr microcontrollers has two layers. – Jot Apr 28 at 23:38
  • Yes, thanks for the clarification. Absolutely, I think one library can't cover all the mcu families, it can focus on one particular family or group of microcontrollers. – R1S8K Apr 29 at 9:35
1

The second parameter to twi_writeTo should be a pointer to a buffer. You have now

twi_writeTo(HMC5883L,0x00,1,0,1);

which sends a byte read at address 0x00. change it to

uint8_t b = 0x00;
twi_writeTo(HMC5883L,&b,1,0,1);

(C arrays are a pointer to first item)

  • Yeah, I did that. It's ok. But the problem is reading the config registers. I can read data registers automatically, but I can't read the configuration registers automatically! I have to do it one by one. – R1S8K Apr 28 at 18:31
  • that is a very different question. the original was about the twi (sub)library. the new is about the specific device. the automatic register increment is to speed up the common use case. with configuration you don't need to hurry – Juraj Apr 28 at 18:48
  • Yeah, sorry, it's actually connected to a device, but maybe the principle is common on i2c devices. Is the problem I'm trying to understand is this feature of communicating with i2c devices. I think there are two types of communication with i2c devices; at least to the HMC5883L. I think if I want to deal with reading configuration registers then I have to do it one by one, if reading data registers, then I can do it automatically. – R1S8K Apr 29 at 9:33
  • 1
    it is device specific, not a feature of I2C – Juraj Apr 29 at 9:36
  • Ah, OK, it's clear that I really don't have that much of information :) Thanks dude for the help, I really appreciate it. First I posted all this lengthy post, and I really don't know what I'm trying to get because there are so much related problems I have to be aware of. I'm thinking of deleting the thread ! What you think ? Do you agree ? and then I arrange my self again and consider on one more beneficial thread. Any recommendations? – R1S8K Apr 29 at 9:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.