analogWrite causes I2C encoder lockup

Hi Arduino experts

I have a really tricky problem that I am totally stumped on.

My code writes a PWM value to a motor H-Bridge. It then reads an I2C encoder position. (using this AS5600 Library: GitHub - kanestoboi/AS5600: An Arduino library for the AS5600 magnetic encoder)

Trouble is, the encoder randomly locks up, when the analogWrite is used.

The final product rotates a table (like a lazy Susan) to a position then stops.

This cut-down version doesn’t do any of that - it’s just to demonstrate how low-level the problem is.
Note that in this demo version, there is no motor or H-Bridge attached - the problem is not motor noise, as there is no motor (or H-Bridge), just the internal PWM generation!

#include <AS5600.h>

//Outputs
#define MOTOR 8
#define LED_PIN 13  //Onboard LED  
#define DIAG_PIN 15



AS5600 encoder;

int measuredEncoderPosition = 0;

byte ticks = 0;


void setup()
{
  //Outputs
  pinMode(LED_PIN, OUTPUT);  //on arduino board

  pinMode(MOTOR, OUTPUT);
  
  Serial1.begin(19200);

  
  // initialize Timer1
  cli();                  // disable global interrupts
  TCCR1A = 0;             // set entire TCCR1A register to 0
  TCCR1B = 0;
  TIMSK1 = (1 << TOIE1);  // enable Timer1 overflow interrupt:
  TCCR1B |= (1 << CS10);  // Set CS10 bit so timer runs at clock speed:
  sei();                  // enable global interrupts:


  


}//end Setup

//------------------------------------------------------------------------------------------
void loop()
{
  Serial1.println("1");

  analogWrite(MOTOR, 100);       //run the motor
  
  Serial1.println("2");
  measuredEncoderPosition = encoder.getPosition(); // get the absolute position of the encoder
  Serial1.println("3");
  Serial1.println(measuredEncoderPosition);
  Serial1.println("4");
}//end Loop


//-----------------------------------------------------------

ISR(TIMER1_OVF_vect)

{
  ticks++;

  if (ticks == 100)
  {
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    ticks = 0;
  }
}

//-----------------------------------------------------------

With the analogWrite disabled:
The encoder will report it’s position each time through the loop
The DIAG_PIN toggles to show a square wave on a scope
The LED_PIN toggles once per second on a timer interrupt.
This will run forever, no worries at all.

With the analogWrite enabled:
The encoder will randomly lock up at some time - a few seconds or a few minutes later.
When it locks up, the last thing printed is always Serial1.println(“2”) - which makes me think that the I2C comms read of the encoder has failed.
The DIAG_PIN stops toggling, and stays flat. (so it can’t be looping any more?)
The LED_PIN continues to toggle once per second on a timer interrupt, showing that the CPU is still alive.

Why would the analogWrite upset the I2C communication? I just don’t understand, and it’s doing my head in.

Things I have tried:
Different encoders, in case it was faulty
Using a different encoder library (the seeed Grove one)
Different Arduino, in case it was faulty
Moving analogWrite to the setup section (so it doesn’t re-write each time thru the loop)
Removing the ISR blinky light (no change)
Different comms baud rate.

I find it hard to believe that a modern powerful micro can’t do two basic things at the same time.
Something funny is going on, but I can’t figure it out.

I would love to receive your expert advice.

Thanks
Pete

Which Arduino? Have You verified that pwm to the motor works? No other code or hardware involved? The same for I2C...

Wiring diagram please....

Oh sorry, I left out that key info!

It’s an arduino Mega 2560 Pro, like this one: 1pcs Mega 2560 PRO-MINI USB-series conversion cable, ATMega2560 MCU for Arduino | eBay

Yes, PWM works perfectly. I can both see it on a scope, and run a real-live motor.
I2C works perfectly (with the PWM off), just not reliably with PWM on at the same time - it locks up after a few mins.

Note also that its the PWM being on that causes the problem - it happens even when the H-Bridge and Motor are disconnected!

Schematic is attached to this message. You will see that I have lots of extra stuff (position switches etc) - none of that is connected, it’s just the Mega, the MAX3232, and the encoder. Motor PWM is on D8 (D9 is pulled permanently low, I don’t actually need to reverse directions). Encoder is on SDA/SCK, which are pins 20 and 21.

Thanks!

logic sch.pdf (46.5 KB)

SCK is SPI, SCL is I2C. Pins 20/21 are SCL and SDA, correct so far.

I don't understand why an encoder with I2C interface deserves a library? If that library happens to use T1 for some purpose, it's fooled by your use of T1. Have you already tried a different timer?

Hi
I use the library because I’m not smart enough to figure it all out myself! A call to encoder.getPosition()just gives me the number I need between 0 and 4095 to tell me where it’s pointing.

I had a look thru the source code of the library, and also the Wire library, but can’t see any reference to any timers.

Have you already tried a different timer?
Do you mean for the PWM? If so, no I didn’t realize that was possible.
Or do you mean for the Timer Interrupt? I haven’t used a different one, but if I disable all the timer code, it still locks up.

I see in the Wire library that it has a timeout function, but the AS5600 encoder library doesn’t seem to look for it at all. I wonder if the encoder is late with a reply occasionally, and the AS5600 library can’t cope, so it just blocks? I know something is causing it to block within the main loop, as the DIAG_PIN stops toggling, but the timer interrupt keeps firing happily.

Does that sound like a possible cause?

Thanks for your help, I’m a bit out of my depth here! :slight_smile:

An I2C transmission can fail, as indicated by the results returned by the various methods, and then should be repeated. If the library does not check the result it may wait indefinitely for a progress, that's why a timeout could help to proceed after a trransmission failure.

Which library are you using? A link please.

The kanstoboi library needs a double data type for the encoder position so that’s one possibility, data being overwritten cause unpredictable behavior.

Also, where are you handling the DIAG_PIN? I don’t see it anywhere in your code.

Which library are you using? A link please.

This one here:

But I’ve also tried the Seeedstudio Grove one, same effect.

The kanstoboi library needs a double data type for the encoder position so that’s one possibility, data being overwritten cause unpredictable behavior.

Ooh, this sounds like an easy thing to try, will give that a go when I get back to work.

Also, where are you handling the DIAG_PIN? I don’t see it anywhere in your code.

Sorry, I wrote so many little test programs that I got confused.
The version with the DIAG_PIN just toggled the pin each time thru the loop:

 analogWrite(TABLE_F, 100); // <-- this is in the setup block, not in the loop

}//end Setup

//------------------------------------------------------------------------------------------
void loop()
{
  measuredEncoderPosition = encoder.getPosition();
  Serial1.print("MEP = ");
  Serial1.println(measuredEncoderPosition);
  
         digitalWrite(DIAG_PIN, !digitalRead(DIAG_PIN));    
         delay(5);  //just for testing (AS5600 in test jig stops communicating for some reason)
  
  delay(100);
}//end Loop

I could see the square wave on a scope, as the program ran thru the loop. When it locked up, the square wave went flat (implying that the program was blocked trying to read the encoder, and not looping), but the timer interrupt led was still blinking, implying that the CPU was still awake.

Thank you for your help! :slight_smile:

DrDiettrich: An I2C transmission can fail, as indicated by the results returned by the various methods, and then should be repeated. If the library does not check the result it may wait indefinitely for a progress, that's why a timeout could help to proceed after a trransmission failure.

So I need to be editing the AS5600 library to check if the Wire library has raised a timeout flag?

thanks Pete

Now it's time to develop your first I2C test program. Use the I2C example code to read results from the sensor, addi checks and error messages for the function results. Then run this part in your final project, instead of the library, and wait for error reports.

I wonder if the interrupt code is causing a problem when it gets called during the AnalogWrite routine - that subsequently caused the I2C communication to lock up. In particular I wonder if the line

  TIMSK1 = (1 << TOIE1);  // enable Timer1 overflow interrupt:

should be changed to

  TIMSK1 |= (1 << TOIE1);  // enable Timer1 overflow interrupt:

To avoid disabling timer0 which is used in the micros routine which is used in the twi routine which is used in the wire routine which is used in the AS5600 routine.

DrDiettrich: Now it's time to develop your first I2C test program. Use the I2C example code to read results from the sensor, addi checks and error messages for the function results. Then run this part in your final project, instead of the library, and wait for error reports.

Hi there

I think you mean that I use the existing TWI library, and talk directly to the encoder, without the encoder library?

Before I go down that path, I think I might have found some evidence that this is a known problem with the Wire library:

There's a heated discussion here about why it hasn't been fixed in 9 years: https://github.com/arduino/ArduinoCore-avr/issues/42

Here's a quick explanation and possible fix: https://github.com/ermtl/Open-Source-Ventilator/blob/master/OpenSourceVentilator/README.md

Now there seems to be a bunch of people who have given up on the Arduino library and wrote their own: https://www.arduinolibraries.info/libraries/jm_wire https://github.com/freespace/SBWire https://rheingoldheavy.com/changing-the-i2c-library/

I have seen more references to jm_wire online, so maybe that's the best one of the 3? If I understand correctly, I need to replace the Wire library with jm_Wire, and #include jm_Wire instead of #include Wire.h wherever it gets called - which is in AS5600.h

Does this sound reasonable, or am I (more) confused?

Thanks again for your time. Pete

You can install any number of libraries provided they have different names. Then #include the library you want to use.

In case other libraries refer to the standard Wire.h, you can uninstall that library and wait for compiler errors. Then either rename your preferred library into Wire.h, Wire.cpp etc., or update the other libraries to include your preferred library instead of Wire.h.

DrDiettrich: You can install any number of libraries provided they have different names. Then #include the library you want to use.

In case other libraries refer to the standard Wire.h, you can uninstall that library and wait for compiler errors. Then either rename your preferred library into Wire.h, Wire.cpp etc., or update the other libraries to include your preferred library instead of Wire.h.

So I have it working with jm_Wire, and it has (sort of) stopped the lockup problem!

Rather than freezing in the loop, as it did before, the loop now continues running (the frozen value is continually written on the screen)

Sadly, after a couple of minutes running, the encoder stops communicating, and returns the value 3839, which is the same as you would see if you unplugged it.

So, I guess we have answered the question "why does it block?", but we haven't answered the question "why does I2C fail when using the PWM, but will run for infinite time when PWM is off?"

There still seems to be some internal conflict that I can't figure out!

Would love to hear any more suggestions! Thanks Pete

Edit: I put a large (50ms) delay in the main loop, seems to have calmed it down nicely. I would still love to solve it officially, but this cover-up seems to work OK!

Edit 2: Fails after about 10 mins - Encoder value is 3839, counter still increasing, so jm_Wire is doing it's job and preventing blocking.

Please check your TIMSK1 assignment statement.

jjhdnd: Please check your TIMSK1 assignment statement.

Hi, thanks for your suggestion, I added the |= as per your message. Sadly, that's not the answer.

I have actually tried removing that whole timer interrupt block, just in case - but no luck, the problem is not with that.

You seem to have an in-depth understanding of the timers - does the PWM (analogWrite) and I2C use the same timer?

The pin you are using is connected to Timer4. I don't see any apparent interference with the Wire routine. While I2C has problems and I often use a time-out wrapper to keep my programs from hanging - you don't experience a problem unless calling AnalogWrite. The only suggestions I have at present are: 1) Make extra sure there is no chance that there is an intermittent connection/short in the hardware. and: 2) Try forcing the compiler to call AnalogWrite with a 16 bit value.

  int PWMvalue = 100;             // force the use of 16 bit value
  analogWrite(MOTOR, PWMvalue);       //run the motor

You could also try using values of 0 and 255 since those are treated as special cases and the routine doesn't access the timer for those values.

Just curious, are you using a Seeed board or something similar for the AS5600?

jjhdnd:
The pin you are using is connected to Timer4. I don’t see any apparent interference with the Wire routine. While I2C has problems and I often use a time-out wrapper to keep my programs from hanging - you don’t experience a problem unless calling AnalogWrite. The only suggestions I have at present are:

  1. Make extra sure there is no chance that there is an intermittent connection/short in the hardware.
    and:
  2. Try forcing the compiler to call AnalogWrite with a 16 bit value.
  int PWMvalue = 100;             // force the use of 16 bit value

analogWrite(MOTOR, PWMvalue);      //run the motor




You could also try using values of 0 and 255 since those are treated as special cases and the routine doesn't access the timer for those values.

Just curious, are you using a Seeed board or something similar for the AS5600?

Hi, thanks for your continuing help, I really appreciate it.

I’m super confident about the hardware, as the fault happens even without the H-bridge or motor attached - it gets upset just because it’s running PWM inside the chip!

I have tried two kinds of encoder - one PCB from Aliexpress, and one encoder that I built. However, the encoder that I built still uses Aliexpress chips.

I am getting very suspicious of the encoder chips, and have some genuine ones on the way from Digikey!

Hi Everyone (including future people who found this thread and have the same question)

Thanks to the non-blocking nature of jm_Wire, it is possible to continue communicating with the AS5600 chip, even when it's sending nonsense values (a value of 3839 means it's locked up).

Reading the Status value, I found that it would report Status 32 when working fine, and report Status 56 when locked up and sending 3839.

It is therefore very easy to check for a good status, and reset the I2C/TWI comms if necessary.

 if (encoderStatus == 56)    //Status 56 = locked up
 {
   Wire.begin();                          //reset the I2C comms
   Serial1.println("Resetting");
   delay(2000);                          //pause for a human to notice and grab a screen shot
 }

Still unexplained:

Status Value 56 means that the chip is reporting both Magnet Strength too HIGH, and Magnet Strength too LOW, at the same time. I would think this should be impossible!

No worries, I can now detect and recover from an error condition!

Thank you to all you wonderful people who offered your suggestions and helped me find a solution. :-)

Pete

If you look at the alternative outputs (analog or PWM) from the AS5600, do they lock up, or do they continue to report correct position?