Device-os: [Gen3] I2C freezes when PWM is active

Created on 10 Dec 2020  路  13Comments  路  Source: particle-iot/device-os

Bug Report

Expected Behavior

I2C should continue working when PWM is active.

Observed Behavior

Device freezes when I2C happens during active PWM.

Steps to Reproduce

This sketch breaks with 2.0.0-rc.1. I just tested it with pin A6. Sketch was tested on an B5SOM.

Test App

SYSTEM_MODE(MANUAL)

#define PIN_BUZZER              A6

void setup() {
    Serial.begin();
    Wire.begin();
}

unsigned long lastPoll = 0;
bool buzzerActive = false;

void loop() {
    if (millis() >= 10000 && !buzzerActive) {
        buzzerActive = true;
        tone(PIN_BUZZER, 3000, 0xffffffff);
    }

    if (millis() - lastPoll > 1000) {
        lastPoll = millis();
        Wire.beginTransmission(0x55);
        Wire.write(0x2C);
        Wire.endTransmission(false);
        Wire.requestFrom(0x55, (uint8_t) 2);

        uint16_t result =Wire.read();
        result |= Wire.read() << 8;
        Serial.printlnf("Value: %d", result);
    }
}
track

Most helpful comment

@XuGuohui Not entirely correct. Detection doesn't happen at all if it's not enabled (it's a persistent setting, which most likely got enabled by tinker out of the box).

@perotom

// Disable power management detection feature
STARTUP(System.disable(SYSTEM_FLAG_PM_DETECTION));

// Or apply default power management configuration which on platforms with no onboard PMIC will disable detection as well
// Pretty much does the same thing.
System.setPowerConfiguration(SystemPowerConfiguration());

All 13 comments

Thanks for the report @perotom. I tried to reproduce this quickly with an Argon running both 1.5.2 and then 2.0.0 but was unable to reproduce. My test code is pasted below. I believe that, in essence, the BMP280 driver is making the same i2c calls to retrieve pressure and temperature. Unfortunately I don't have a b5som available right now to test with. @perotom Are you able to reproduce this with 2.0.0 (not an rc release but the default 2.0.0 release) on any other hardware?

#include <Adafruit_SSD1306_RK.h>
#include <Adafruit_GFX_RK.h>
#include <Adafruit_BMP280.h>
#include <Particle.h>
#include <Wire.h>

SYSTEM_MODE(MANUAL)

#define PIN_BUZZER              A0

unsigned long lastPoll = 0;
bool buzzerActive = false;

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
// SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_BMP280 bmp; // I2C


float g_last_celsius_read = 0.0;

void display_setup() {

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
  }

  // Clear the display buffer
  display.clearDisplay();
  display.setTextSize(1);      

  display.display();
  delay(2000);

}

// sensor readings locally
void render_to_oled() {
  //display.clearDisplay();

  int xoff = 0;
  int yoff = 0;
  display.setTextColor(WHITE);

  // time display
  display.setCursor(xoff, yoff);             

  if (lastPoll % 2 == 0) {
    display.println(F("Gorgonzola"));
  }
  else {
    display.println(F("Emmenthaler"));
  }
  yoff += 14;
  display.setCursor(xoff, yoff);   
  display.println(lastPoll);

  // flush to oled
  display.display();

  //ensure we write to the display
  //delay(2000);
}

void setup() {
    Serial.begin();
    Wire.begin();

    if (!bmp.begin(0x76)) {
        Serial.println("BMP setup failed!");
    }

    display_setup();

}

void loop() {
    if (millis() >= 10000 && !buzzerActive) {
        buzzerActive = true;
        tone(PIN_BUZZER, 15, 0xffffffff);
    }

    if (millis() - lastPoll > 1000) {
        g_last_celsius_read = bmp.readTemperature();
        float press = bmp.readPressure();
        render_to_oled();
        lastPoll = millis();
        Serial.printlnf("ok %d press: %f", lastPoll, press);
    }
}

Thanks for checking.
Yes the bug persists throughout every 2.0.X release, also the latest 2.0.0. It would be great if you could check it with a b5som.
Edit: It is quite hard for me to test it with another particle model as our device is built for the bsom.

Thanks for the additional info. I'll try with the Boron next as that's probably the closet thing I have immediately available.

I think I might be able to buy a B402 and try it.

@perotom This is definitely reproducible on at least B5 SoM and seems to be something with tone/pwm HAL specifically.

After diving deeper, it's just a pin usage conflict. The A6 being used for tone function is used as the INT pin of power management. This applies on B5SoM, BSoM only. The root cause is that the A6 pin is configured as an interrupt source for power management, once the device starts tone on the A6 pin, continuous rising/falling edges will occur on the A6 pin, which result in that the power management interrupt handler is triggered continuously. Thus, from the user's standpoint, the device is frozen, as the user application thread cannot be executed any more.

@avtolstoy I'm going to introduce a new pin function PF_BUILTIN in pinmap_hal.h, which to avoid the pin usage conflicts, like the pins used for power management and the ethernet wing. Any thought?

:facepalm: True, A6 is supposed to be PMIC/FG interrupt. Thanks for looking into this.

I'm going to introduce a new pin function PF_BUILTIN in pinmap_hal.h, which to avoid the pin usage conflicts, like the pins used for power management and the ethernet wing. Any thought?

So that when needed (since power management on SoMs except for T SoM is optional, same goes for Ethernet) we can mark the pin as in-use by Device OS and guard against erroneous access? Sounds good. The only concern is potential overhead of additional checks perhaps.

Great work 馃憣馃徏
So if we don't use the PMIC this issue should not happen right? If yes, how do I disable the PMIC explicitly?
Thanks for looking into this!

Maybe PF_SYS makes more sense in this scenario. I think being a bit overhead of additional checks is better than messing up things like a "bug" without throwing any error code to user application.

So if we don't use the PMIC this issue should not happen right? If yes, how do I disable the PMIC explicitly?

Try adding the below code snippet in your application

STARTUP( []{
    System.setPowerConfiguration(SystemPowerConfiguration().feature(SystemPowerFeature::DISABLE));
} );

But I think as this issue is encountered on your custom development board, the device os must have detected the PMIC or Fuel Gauge. If there is neither PMIC nor Fuel Gauge on your board, it won't encounter the issue. The code above just disables the power management by device os, while you need to take control of the PMIC or Fuel Gauge using the corresponding libraries provided in wiring.

@XuGuohui Not entirely correct. Detection doesn't happen at all if it's not enabled (it's a persistent setting, which most likely got enabled by tinker out of the box).

@perotom

// Disable power management detection feature
STARTUP(System.disable(SYSTEM_FLAG_PM_DETECTION));

// Or apply default power management configuration which on platforms with no onboard PMIC will disable detection as well
// Pretty much does the same thing.
System.setPowerConfiguration(SystemPowerConfiguration());

Thanks, I tried it and it fixed the problem!
Awesome guys thank you very much!

The outcome of an internal discussion about how we improve this is, as the first step we will just document the shared pins for now. @rickkas7 for your information.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

technobly picture technobly  路  3Comments

memaskal picture memaskal  路  5Comments

technobly picture technobly  路  3Comments

technobly picture technobly  路  5Comments

mdgagne picture mdgagne  路  11Comments