Having issues with my simple I2C program

Hi All. Hoping someone can shed some light on an issue I seem to be having.

I am using a PICAXE with I2C to send a 16bit value to an ATtiny85. The ATtiny does some maths and then upon request returns the result to the PICAXE.

At this point I don't know how I can debug my program to know what is actually going on, but my simple I2C program appears to be hanging at times.

I have identified that the issue appears to be in multiples of 128 for some reason, but I am not understanding the root of the problem.

Writing a simple program in the PICAXE (a FOR/NEXT loop 0 to 20000), the ATtiny operates fine until the PICAXE sends 127 at which point the ATtiny returns 79, which is correct. However it continues to return 79 until the PICAXE value hits 255. The ATtiny then starts returning the correct answer again (160) up until the PICAXE hits 382. Things get going again when the PICAXE hits 512 and so on. As can be seen in the program, the LED on PB4 goes HIGH/LOW as expected when the calculated value is returned, but does not go HIGH/LOW when the repeated incorrect value is being returned. During this time the LED just stays OFF/LOW. Once everything gets running normal again (PICAXE hits 255) the LED starts going HIGH/LOW again as it should.

For the life of me, I can't appreciate what or why this is actually happening. I am guessing some sort of buffer issue, but why?

PICAXE simple test program...

for w4 = 0 to 20000

sertxd("Sent: ",#w4,cr)

hi2cout [$26], (b8,b9)		'Send 16bit ADC value in two bytes format to ATtiny85-20SF Math Co-processor for calculation
pause 100				'Give the ATtiny85-20SF time to perform the calculation and be ready with the result
hi2cin [$26], (b2,b3)		'Receive 16bit WORD ohms value in two bytes format from ATtiny85-20SF. 12345 = 123.45 Ohms

sertxd("Returned: ",#w1,cr,cr)

next

end

The rather simple and short FULL ATtiny program...

#include <Wire.h>                                     // Include Arduino TinyWire library for I2C

#define SLAVE_ADDR 0x13                               //Define Slave I2C Address. PICAXE equiv = times 2 (Shift value LEFT by 1 bit)
                                                      //Arduino 0x12 = PICAXE $24

word adc_value;                                       //Received ADC Value from MASTER
float adc_Ref_Volts = 2.048;                          //ADC Reference Voltage
int VRef_Volts = 5;                                   //Voltage at top of 1K 0.01%
word adc_Max = 65535;                                 //Maximum possible ADC Value
float adc_Volts;                                      //Calculated Volts referenced from adc_value and adc_Ref_Volts
int R1 = 1000;                                        //Top resistor in voltage divider (1K 0.01%)
float R2;                                             //Final calculated unknown resistor value in Ohms xxx.xx
word R2word;                                          //R2 as WORD



void setup() {
  pinMode(4, OUTPUT);                                 //Initialise PB4 as OUTPUT for LED visual confirmation

  Wire.begin(SLAVE_ADDR);                             //Initialize I2C communications as Slave

  Wire.onReceive(receiveEvent);                       //Function to run when data received from master

  Wire.onRequest(requestEvent);                       //Function to run when data requested from master

  digitalWrite(4, HIGH);
  delay(1);
  digitalWrite(4, LOW);
}


void receiveEvent() {
  while (0 < Wire.available()) {
    adc_value = Wire.read();                          //Receive low byte as lower 8 bits
    adc_value |= (word)Wire.read() << 8;              //Receive high byte and shift left to be high 8 bits of 16bit WORD
  }

  digitalWrite(4, HIGH);                              //Activate LED confirming DATA has been received

  adc_Volts = (adc_value * adc_Ref_Volts) / adc_Max;  //Calculate ADC Volts
  R2 = (adc_Volts * R1) / (VRef_Volts - adc_Volts);   //Calculate Unknown Resistor Value

  R2 = R2 * 100;                                      //Shift decimal 2 places to the right
  R2word = R2;                                        //Convert R2 Float to a 16bit WORD
}


void requestEvent() {
  Wire.write(R2word & 0xFF);                          //Send 16bit WORD result in two 8bit bytes via I2C to Master
  Wire.write(R2word >> 8);

  digitalWrite(4, LOW);                               //Deactivate LED confirming DATA has been successfully returned
}





void loop() {
}

So what may be going on here?

A couple questions I'd also like answered...

  • Which order to send the two 8 bit bytes to the ATtiny? high 8 first, or low 8 first?

  • Throughout the ATtiny program... high 8 first, or low 8 first? Does it really matter?

Any help here is greatly appreciated.

Cheers,
Mort.

Which byte (is it lower-byte) that is being transmitted first?

Is Mater sending the lower-byte first?

A Master-Slave configuration on the I2C bus works with Arduino Uno boards (and other boards of the AVR family) with the Arduino software. For every other board you can not be sure that it will work :grimacing:

The I2C bus must have pullup resistors, and there should be a GND connection between the two.
The Master should check if the I2C session was successful and it must support clock pulse stretching. If you can not confirm that it supports clock pulse stretching then it will not work.

The Slave sketch can be improved.
Do you have a parameter with the receiveEvent() ?
There is no need for a while-loop, there should be only two bytes.
Please show how all the conversions go, by writing the cast in the source code.
Please use local variables when a variable is only used locally.

void receiveEvent( int howMany)
{
  if( howMany == 2)   // extra check for safety, two bytes expected
  {
    adc_value = Wire.read();              // Receive low byte as lower 8 bits
    adc_value |= (word)Wire.read() << 8;  // Receive high byte and shift left to be high 8 bits of 16bit WORD

    digitalWrite(4, HIGH);                // Activate LED confirming DATA has been received

    adc_Volts = ( float(adc_value) * adc_Ref_Volts) / float(adc_Max); // Calculate ADC Volts
    R2 = (adc_Volts * float(R1)) / (float(VRef_Volts) - adc_Volts);   // Calculate Unknown Resistor Value

    R2 = R2 * 100.0;                      // Shift decimal 2 places to the right
    R2word = (word) R2;                   // Convert R2 Float to a 16bit WORD
  }
}

That is a very weird calculation. There is so much conversion between integer and float, I don't know if the calculation works.

Suppose you do everything right what I wrote here, then I still can not guarantee that it will work. If one of the two has timing issues, then it will be almost impossible to find those issues and fixing it might be impossible.

Do you have a usb-logic-analyzer ?

Does receiveEvent(int howMany) trigger after receiving each byte from the Master; or it triggers after receiving all the bytes in between the begin.Transmission() and end.Transmission()? Practically, the int howMany variable is always equal to the number of bytes received from Master.

The onReceive handler is called when all the bytes have been received.
The SAMD had/has issues, it could/can trigger with zero bytes.
In case something is wrong on the I2C bus, it also provides extra safety.

Therefor I prefer that extra safety check.

1 Like

Why? Because Arduino can do floating point maths?

Have you considered replacing the PICAXE with an Arduino? It might be easier than getting two different MCU to communicate. Even if it takes a little more effort, it would be worthwhile. Once you get a little more used to Arduino you won't want to go back to PICAXE, that was my experience.

Which byte (is it lower-byte) that is being transmitted first?
Is Mater sending the lower-byte first?

From the PICAXE perspective, either can be sent first. The 16bit value is in W4 which comprises of b9 & b8 8bit bytes. Is there a correct order to send them to the ATtiny?

Cheers,
Mort.

If you are sending lower byte first, then the byte being read first in the Slave side is the lower byte.

Does the above code mean that you are sending lower byte first?

I am very much a noob with Arduino, however I am fluent with PICAXE.

Sorry, I don't actually know what you mean with this question.

I altered this as you suggested. No change to the result, but thank you for the improvement.

Everything does calculate just fine between integers and floats etc. The calculations are simply the basic calculations required to determine a resistance value from an ADC value. The ADC I am using is also on the I2C bus and has ultra precision voltage references connected to it of 2.048v and 5.000v. The ADC chip I am using is a LTC2485 (24bit ADC), however I am using only the top 16bits of the conversion result so as to absolutely eliminate any errors (offset or otherwise). I am an experienced electronics hardware design engineer, and have written this ATtiny code from research, trial and error.

The circuit does have 4.7k pullup resistors on the I2C bus and a common GND.

A browse over on the PICAXE forum has the PICAXE guru's suggesting that PICAXE will/does support clock pulse stretching automatically. The I2C peripheral of the PICAXE MASTER is hardware based in my case and is a PICAXE 28X2. I have used I2C with a PICAXE as a MASTER in many other cases with many other chips and have yet to come across any issues of timing/clock pulse stretching.

While this Arduino program is relatively short and fairly straight forward, I have to admit I am well beyond the limit of my confident Arduino programming knowledge.

I am sorry, I don't understand what it actually means to/when to use 'local variables' vs global variables. When initially writing this code, I was trying to use variables in the way that I thought was 'local' within the void Events, but the compiler was grizzling at something to do with wanting global variables.

At this stage I feel I am rather unhelpful to my own cause by just not properly knowing/understanding/appreciating how variables are and or need to be structured within Arduino code.

I kinda wish someone well experienced with Arduino coding could/would re-write what I have done (perhaps in a corrected manner). As I say, the math part of it does appear to work just fine.

By altering the PICAXE test program to:

w4 = 20
gosub calculate

w4 = 112
gosub calculate

w4 = 128
gosub calculate

end



calculate:
sertxd("Sent: ",#w4,cr)

hi2cout [$26], (b8,b9)		'Send 16bit ADC value in two bytes format to ATtiny85-20SF Math Co-processor for calculation
pause 500				'Give the ATtiny85-20SF time to perform the calculation and be ready with the result
hi2cin [$26], (b2,b3)		'Receive 16bit WORD ohms value in two bytes format from ATtiny85-20SF. 12345 = 123.45 Ohms

sertxd("Returned: ",#w1,cr,cr)

pause 500

return

From the PICAXE terminal screen:

Sent: 20
Returned: 12

Sent: 112
Returned: 70

Sent: 128
Returned: 70

The sent 20 & 112 returned correct calculated results, meanwhile sending 128 and having 70 also returned is incorrect.

For this test, I just randomly picked 20 and 112 (both being below 127) and purposely picked 128.

This result clearly shows me the issue is something to do with the BYTE value and certainly nothing to do with timing/clock pulse stretching.

The fact that it returns the same calculated number (after a byte value of 128) tells me it is performing the 'requestEvent' just fine, but the calculation (with new data) stops being processed in the 'receiveEvent' for some reason. If this wasn't the case, then I would have expected the PICAXE to show a 'Returned: VALUE' of 65535 being that nothing actually came back from the Attiny.

Received values 0-127 = Calculated/processed by the ATtiny as expected via receiveEvent
Received values 128-255 = WRONG. ATtiny does not process/calculate the data via receiveEvent
Received values 256-382 = Calculated/processed by the ATtiny as expected via receiveEvent
Received values 383-511 = WRONG. ATtiny does not process/calculate the data via receiveEvent

I have now identified that when the LOW byte's 7th BIT is a 1, the program returns the last calculation result when the 7th BIT was a 0. The program does this until the 7th BIT becomes a 0 again. So basically (I think) I am seeing ANY 16bit value that the ATtiny receives that has a 1 in the 7th BIT causes the receiveEvent not to run.

No I do not have a logic analyser on hand atm.

Cheers,
Mort.

Yes, you are correct. I am indeed using the ATtiny for it's floating point maths.

At this stage my preference remains primarily using PICAXE as I am quite fluent with PICAXE and for now there is no real point to re-inventing the wheel so to speak. This application is the only time I have had to step away from PICAXE.

Cheers,
Mort.

Yes, you are correct. b8 is the lower byte and is sent first.

Essentially in PICAXE land, W4 is a 16bit WORD that comprises of b8 low byte and b9 high byte. And of course I can send in any order I choose.

I have previously harped a little on this BYTE ORDER thought in other posts, but am still not clearly understanding.

My query here is more so does...

adc_value = Wire.read();                          //Receive low byte as lower 8 bits

Place the first received BYTE into the lower byte of adc_value being a 16bit WORD?

and does...

    adc_value |= (word)Wire.read() << 8;              //Receive high byte and shift left to be high 8 bits of 16bit WORD

Place the second received BYTE into the high byte of adc_value being a 16bit WORD?

I am asking this just so as to clarify that I am doing what I think I am doing correctly.

At the end of the day, when the math is processed, the result comes out correct, I just know if I am actually just flipping things around unnecessarily is really all I am trying to appreciate.

Also due to simply being 'unsure', I changed all variables I defined as word to uint16_t, but doing this made absolutely no difference whatsoever. I am guessing the compiler sees them both as the same thing.

Cheers,
Mort.

Further additional info of this problem. To simplify matters, I removed the math and made the program receive the two bytes, place them in high and low bytes of the 16bit word/uint16_t adc_value and then just return them back to the PICAXE. Same problem occurs.

ATtiny code:

#include <Wire.h>                                     // Include Arduino TinyWire library for I2C

#define SLAVE_ADDR 0x13                               //Define Slave I2C Address. PICAXE equiv = times 2 (Shift value LEFT by 1 bit)
                                                      //Arduino 0x12 = PICAXE $24


volatile uint16_t adc_value;                          //Received ADC Value from MASTER
float adc_Ref_Volts = 2.048;                          //ADC Reference Voltage
int8_t VRef_Volts = 5;                                //Voltage at top of 1K 0.01%
uint16_t adc_Max = 65535;                             //Maximum possible ADC Value
float adc_Volts;                                      //Calculated Volts referenced from adc_value and adc_Ref_Volts
uint16_t R1 = 1000;                                   //Top resistor in voltage divider (1K 0.01%)
float R2;                                             //Final calculated unknown resistor value in Ohms xxx.xx
volatile uint16_t R2word;                             //R2 as WORD


void setup() {
  pinMode(4, OUTPUT);                                 //Initialise PB4 as OUTPUT for LED visual confirmation
  Wire.begin(SLAVE_ADDR);                             //Initialize I2C communications as Slave
  Wire.onReceive(receiveEvent);                       //Function to run when data received from master
  Wire.onRequest(requestEvent);                       //Function to run when data requested from master

  digitalWrite(4, HIGH);
  delay(1);
  digitalWrite(4, LOW);
}


void receiveEvent( int howMany)
{
  if( howMany ==2)                                      // extra check for safety, two bytes expected
  {
    digitalWrite(4, HIGH);                              //Activate LED confirming two BYTE's of DATA has been received
    adc_value = Wire.read();                            //Receive low byte as lower 8 bits
    adc_value |= (word)Wire.read() << 8;                //Receive high byte and shift left to be high 8 bits of 16bit WORD
  }
}

void requestEvent() {
  Wire.write(adc_value & 0xFF);                          //Send 16bit WORD result in two 8bit bytes via I2C to Master
  Wire.write(adc_value >> 8);
  digitalWrite(4, LOW);                               //Deactivate LED confirming DATA has been successfully returned
}

void loop() {
}

PICAXE teminal screen result:

Sent: 124
Returned: 124

Sent: 125
Returned: 125

Sent: 126
Returned: 126

Sent: 127
Returned: 127

Sent: 128
Returned: 127

Sent: 129
Returned: 127

Sent: 130
Returned: 127
.
.
.
Sent: 253
Returned: 127

Sent: 254
Returned: 127

Sent: 255
Returned: 127

Sent: 256
Returned: 256

Sent: 257
Returned: 257

Sent: 258
Returned: 258

Sent: 259
Returned: 259

Sent: 260
Returned: 260

This really makes no intelligent sense to me :roll_eyes:

Cheers,
Mort.

It makes no sense, but I have a far-fetched idea.
Suppose there is a conversion in the PIXAXE for characters and those characters are signed 8-bit. Then a value of 128 is not possible. Perhaps there is a weird conversion. It is even possible that a value of 128 does not even generate a I2C session and you are reading the old value.

I'm very fond of the LHT00SU1 Logic Analyzer in combination with PulseView/sigrok. It was 20 dollars/euros in the past, but today it is between 35 and 40 dollars/euros.

Maybe try

void receiveEvent( int howMany)
{
  if( howMany ==2)                                      // extra check for safety, two bytes expected
  {
    digitalWrite(4, HIGH);                              //Activate LED confirming two BYTEs of DATA has been received
    byte highByte = Wire.read(); //Receive high byte
    byte lowByte = Wire.read(); //Receive low byte
    adc_value = word(highByte, lowByte);
  }
}

void requestEvent() {
  Wire.write(lowByte(adc_value));                          //Send 16bit WORD result in two 8bit bytes via I2C to Master
  Wire.write(highByte(adc_value));
  digitalWrite(4, LOW);                               //Deactivate LED confirming DATA has been successfully returned
}

However, it seems to me your code is receiving the 2 bytes in one order and sending them back in the opposite order, so try swapping some of the code lines above around until you get the right answer.

Is your setup with 'i2cslow' and 'i2cbyte' ?

No.

The setup is...

HI2cSetup I2CMASTER, $26, I2CFAST_64, I2CBYTE

I did change it to I2CSLOW, however the ATtiny didn't respond.

The ATtiny is currently operating at 8MHz internal osc.

Cheers,
Mort.

If 100kHz does not work and 400kHz does not work properly, then I say that you have no I2C bus communication.
Do you have pullup resistors ? Is the PIXAXE running at 3.3V ? and the ATtiny at 5V ?

I made slight change, but effectively applied your suggestion. Sadly, the same result.

void receiveEvent( int howMany)
{
  if( howMany ==2)                                      // extra check for safety, two bytes expected
  {
    digitalWrite(4, HIGH);                              //Activate LED confirming two BYTEs of DATA has been received
    byte lowByte = Wire.read(); //Receive low byte
    byte highByte = Wire.read(); //Receive high byte
    adc_value = word(highByte, lowByte);
  }
}

void requestEvent() {
  Wire.write(lowByte(adc_value));                          //Send 16bit WORD result in two 8bit bytes via I2C to Master
  Wire.write(highByte(adc_value));
  digitalWrite(4, LOW);                               //Deactivate LED confirming DATA has been successfully returned
}

PICAXE Terminal screen:

Sent: 125
Returned: 125

Sent: 126
Returned: 126

Sent: 127
Returned: 127

Sent: 128
Returned: 127

Sent: 129
Returned: 127

For clarity, this is the PICAXE test program used...

setfreq em64				'Set External Crystal Freq = 64MHz (Crystal 16MHz)


'I2C Communications
HI2cSetup I2CMASTER, $26, I2CFAST_64, I2CBYTE

main:

for w4 = 0 to 20000
   gosub calculate
next

goto main





calculate:
sertxd("Sent: ",#w4,cr)
hi2cout [$26], (b8,b9)		'Send 16bit ADC value in two bytes format to ATtiny85-20SF Math Co-processor for calculation
pause 10				'Give the ATtiny85-20SF time to perform the calculation and be ready with the result
hi2cin [$26], (b2,b3)		'Receive 16bit WORD ohms value in two bytes format from ATtiny85-20SF. 12345 = 123.45 Ohms
sertxd("Returned: ",#w1,cr,cr)
return

Cheers,
Mort.

Well... at 100kHz(I2CSLOW), the ATtiny didn't respond at all, however at 400kHz(I2CFAST), if I run it (inclusive of the math) with a FOR/NEXT loop 0 to 127 and keep repeating the FOR/NEXT loop, everything works perfectly as intended. Math is correct, numbers increment and are returned as expected.

Testing well into the thousands, the same applies. The problem clearly ONLY occurs when the LOW BYTE has a 1 in BIT7.

Absolutely, I do indeed have pull up resistors (4.7k) on the SCL and SDA lines.

Both PICAXE and ATtiny85 are operating at 5V from the same very clean (well filtered) supply and each have a 100nF decoupling cap right at the supply pins.

Cheers,
Mort.

The way I look at it, then that byte that you accidentally received correctly is futile.
I would focus on getting the 100kHz working.
Can you get a logic analyzer from somewhere ?

Can you show a photo of your project with the wiring ?
Sometimes jumper wires are broken and breadboards can have bad contacts.