Questions about "utility/twi library"

Hello,

I’m just trying to learn things about programming core libraries.

My reasons to investigate and try to learn from core libraries are:

  1. Passion and curiosity for for learning how to program the internal hardware
  2. Developing my experience in programming, time after time because my first time I started to get into studying the code libraries, I didn’t know pretty much things about; for example, program control, FSM, ISR control … etc.
  3. Improve my programming skills in writing device drivers; because if I got something I need to program and didn’t find some meaningful libraries on the web, then I can program my own library. And this reason is one of the most important reasons.

=================================================================

My first time learning how to do core libraries like; twi, spi, uart … etc. I now wrote these libraries in the most basic form, taking the basic functions from the Atmega328 datasheet. At that time I thought that’s pretty much about it. And didn’t understand why the premium libraries are much more complex and there is a lot of data flow management and program flags control.

Basically, my I2C library works OK, but if something goes wrong in the program, then there’s no management in my code to restart the initialization of i2c bus.

There are still a lot of things to learn about, especially in this library.

=================================================================

My question now, is that I copied the utility/twi, changed the extension of the source file to .cpp and copied the whole library and put it in Arduino/libraries, and tried to run one basic program to get data from HMC5883L, but the code didn’t work. But it’s working with my i2c library.

I know that I should use the Wire.h library but I wanted to know why I can’t use the twi.h library and use it?

So, I don’t know what’s wrong with it. The code takes one arbitrary value and put it in the serial monitor only one time, there’s no continuous reading from the HMC5883L.

This is my code:

#include "twi.h"

////////////////////////////////////////////////////////////////////////////////////
// HMC5883L
//#define HMC5883L                  0x1E
#define HMC5883L_READ               0X3D
#define HMC5883L_WRITE              0X3C
#define CONFIGURATION_REGISTER_A    0X00  //Read/Write
#define CONFIGURATION_REGISTER_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 data_read (uint8_t *dat);
void hmc5883l_print_serial(void);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  twi_init();                 // frequency is already set by default to 100k
  twi_setFrequency(100000);   // if you want to set it to different speed
}

void loop() {
  // put your main code here, to run repeatedly:
  hmc5883l_print_serial();
}



void hmc5883l_init(void){
  uint8_t HMC5883L_ini[4] = {CONFIGURATION_REGISTER_A,0x78,0x20,0x00};
    twi_writeTo(HMC5883L_WRITE,HMC5883L_ini,4,1,1);
}

void data_read(uint8_t *dat){
  bool write_res,read_res;
  uint8_t op_x_msb[] = {0X03};
  write_res = twi_writeTo(HMC5883L_WRITE,op_x_msb,1,1,0);
  read_res = twi_readFrom(HMC5883L_READ, dat, 6, 1);
}

void hmc5883l_print_serial(void){
  int16_t x,z,y;
  uint8_t data[6];
  float heading,heading_in_degrees,declination_angle_YANBU;
  
  data_read(data);
  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;
  
  // Correct for heading < 0deg and heading > 360deg
  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);
  

}

use the basic address. the twi functions convert it to read/write address

SAME PROBLEM …

#include "twi.h"

////////////////////////////////////////////////////////////////////////////////////
// HMC5883L
#define HMC5883L                    0x1E
#define HMC5883L_READ               0X3D
#define HMC5883L_WRITE              0X3C
#define CONFIGURATION_REGISTER_A    0X00  //Read/Write
#define CONFIGURATION_REGISTER_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 data_read (uint8_t *dat);
void hmc5883l_print_serial(void);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  twi_init();                 // frequency is already set by default to 100k
  twi_setFrequency(100000);   // if you want to set it to different speed
}

void loop() {
  // put your main code here, to run repeatedly:
  hmc5883l_print_serial();
}



void hmc5883l_init(void){
  uint8_t HMC5883L_ini[4] = {CONFIGURATION_REGISTER_A,0x78,0x20,0x00};
    twi_writeTo(HMC5883L,HMC5883L_ini,4,1,1);
}

void data_read(uint8_t *dat){
  uint8_t write_res,read_res;
  uint8_t op_x_msb[] = {0X03};
  write_res = twi_writeTo(HMC5883L,op_x_msb,1,1,0);
  read_res = twi_readFrom(HMC5883L, dat, 6, 1);
  //Serial.print("WRITE RESULT  ");Serial.println(write_res);
  //Serial.print("READ RESULT   ");Serial.println(read_res);
}

void hmc5883l_print_serial(void){
  int16_t x,z,y;
  uint8_t data[6];
  float heading,heading_in_degrees,declination_angle_YANBU;
  
  data_read(data);
  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;
  
  // Correct for heading < 0deg and heading > 360deg
  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);
}

I tested the maser library for the HMC5883L and it's working perfectly. It's just my library isn't perfect as should be.

I need to improve my coding a lot to get stable program codes.

I don't see any other "twi" error in your code. Could it be an error in use of registers of the device?

Yeah, maybe. But anyway I'm testing things with the twi library as I didn't encounter any examples online about this library, because it's the core library for Arduino Wire library.

Maybe it's little more hard to use it alone without it's C++ application library.

I better to use the Wire library and start working on a more serious programming than trying to investigate libraries about twi.

But, and it's the most important, I've been trying to understand the twi driver for Arduino, and I didn't completely understand it right away, it took me time along months of going back and forth and developing small core libraries in AVR environment. Of course, my learning curve should be improved after learning something new everyday.

So, yeah, I now understood, how things are handled in twi, and also have downloaded other libraries; like, Peter Fleury, ChrisHerring .. etc.

Hmm, I don't what I want to do exactly, I don't want to rely on the Wire library because of the code overhead.

But anyway it's a reliable library and if I needed more program memory, then I should go to a larger chip and that's it.

the twi.h/.c is AVR library for I2C avr-libc: Example using the two-wire interface (TWI)

Wire is Arduino wrapper

Juraj:
the twi.h/.c is AVR library for I2C avr-libc: Example using the two-wire interface (TWI)

Wire is Arduino wrapper

Absolutely, and the code library for Wire library is included in the same folder as its driver code.

So, when I tried to use that core twi library that is basically written for Wire, it didn’t work !

But i liked that library so much, I’ve trying to learn that core library for years. I just started to understand some of it now. And now I’m trying to copy the same library and write it again with understanding each line.

As I’m writing the library, I just came up to this check with if statement, to check if the index is less than the argument length after actually the function finished the read operation in the ISR - at least that how I thought it’s working.

Then if the if statement is true then it moves the index to the input length ! Didn’t understand why this check after the function finished read request?

I would really appreciate it, if anyone explain that to me.

This is the whole function, only with the required ISR section. Not putting the whole code, it’s about 500 lines.

The line I’m asking about is at the end of the read function, putting an arrow for clarification.

/* 
 * Function twi_readFrom
 * Desc     attempts to become twi bus master and read a
 *          series of bytes from a device on the bus
 * Input    address: 7bit i2c device address
 *          data: pointer to byte array
 *          length: number of bytes to read into array
 *          sendStop: Boolean indicating whether to send a stop at the end
 * Output   number of bytes read
 */
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...
  // On receive, the previously configured ACK/NACK setting is transmitted in
  // response to the received byte before the interrupt is signalled. 
  // Therefor we must actually set NACK when the _next_ to last byte is
  // received, causing that NACK to be sent in response to receiving the last
  // expected byte of data.

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

  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 ourselves, 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(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) // <----------------------------------- this is what I'm asking about
    length = twi_masterBufferIndex;

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



    // Master Receiver
    case TW_MR_DATA_ACK: // data received, ack sent
      // put byte into buffer
      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
      twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
 if (twi_sendStop)
          twi_stop();
 else {
  twi_inRepStart = true; // we're gonna send the START
  // don't enable the interrupt. We'll generate the start, but we 
  // avoid handling the interrupt until we're in the next transaction,
  // at the point where we would normally issue the start.
  TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ;
  twi_state = TWI_READY;
 }    
 break;
    case TW_MR_SLA_NACK: // address sent, nack received
      twi_stop();
      break;

the user requested length count of bytes. if it is more then the size of the internal buffer count of received bytes, then 'length' is set to the size of the buffer count of received bytes, because there is only so much data

Juraj:
the user requested length count of bytes. if it is more then the size of the internal buffer, then 'length' is set to the size of the buffer, because there is only so much data

But at the beginning of the function, there's another check for user length, and if it's more than the actual buffer length, then function terminates and return with 0.

What's the difference between the check of the length with actually two variables .. wait a minute !!

The first check is to check if the user length is more than the actual buffer length that is already defined in the header file.

But the second check is to check if the buffer index is less than the requested length ! Not understanding it completely now, but should acquire the idea later.

Hmm I guess, the second check, is after the function actually finished the read operation for all the requested bytes in the ISR. So that after all this if is checking is the index is still less than the length which means the read operation didn't fully accomplished then just set the length to the actual index

Then, the next for loop is to copy the resulted read bytes into the user passed array.

Then finally return the length that the user actually passed in the first place.

But I guess that if statement isn't very important, because I can do the copy with the index and return the index. How about my idea ? would it do the same job or the programmer way is the more optimized one ?