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
asked Apr 28, 2019 at 15:47
4
  • 1
    That is not the core library, those lower level functions are part of the Wire library. Commented Apr 28, 2019 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 ? Commented Apr 28, 2019 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. Commented Apr 28, 2019 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. Commented Apr 29, 2019 at 9:35

1 Answer 1

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)

answered Apr 28, 2019 at 17:39
6
  • 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. Commented Apr 28, 2019 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 Commented Apr 28, 2019 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. Commented Apr 29, 2019 at 9:33
  • 1
    it is device specific, not a feature of I2C Commented Apr 29, 2019 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? Commented Apr 29, 2019 at 9:40

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.