sparkfun / Arduino_Apollo3

Arduino core to support the Apollo3 microcontroller from Ambiq Micro
83 stars 38 forks source link

Core v2.1.0 : First SPI transaction resets CIPO configuration #407

Open PaulZC opened 3 years ago

PaulZC commented 3 years ago

Background

I’m upgrading the OpenLog Artemis to v2.1 of the core. I need to be able to manually configure the pull-ups on the “qwiic” SCL and SDA pins (D8 and D9) and the SPI CIPO pin (D6).

In v1.2.1 we could call padMode to manually set the pull-up on D6 and that worked just fine. But in v2.1 padMode is no longer supported. I've switched to pin_config and that works, but switching has revealed an interesting feature in v2.1.0 of the core:

This did not happen in v1.2.1…

This feature took a while to pin down and, once you know it is there, is straightforward to work around. I’ve included my detective work below in case anyone finds it useful.

Anyone wanting to jump straight to the work around for OpenLog Artemis will find:

Proof that pin_config works on D8 and D9

In v1.2.1 we could change the I2C pull-ups by calling (e.g.) qwiic.setPullups(). But in v2.1 setPullups is no longer supported.

Just to prove that pin_config does work as a replacement, try this example:

Apollo3 v2.1.0 RedBoard Artemis ATP Connect an oscilloscope to pins D8 and D9 Run this sketch:

#include <Wire.h>

TwoWire qwiic(9,8); //Use pads 8/9 (IOM1)

void setup() {
  qwiic.begin();

  //Change SCL pull-up manually using pin_config
  am_hal_gpio_pincfg_t sclPinCfg = g_AM_BSP_GPIO_IOM1_SCL;
  sclPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_1_5K; // Use 1K5 pull-up on SCL (D8)
  pin_config(D8, sclPinCfg);

  //Change SDA pull-up manually using pin_config
  am_hal_gpio_pincfg_t sdaPinCfg = g_AM_BSP_GPIO_IOM1_SDA;
  sdaPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_1_5K; // Use 1K5 pull-up on SDA (D9)
  pin_config(D9, sdaPinCfg);
}

void loop() {
  qwiic.beginTransmission(0x55);
  qwiic.endTransmission();
}

You should see this (note the sharp rising edges on both signals):

image

Change the code to:

#include <Wire.h>

TwoWire qwiic(9,8); //Use pads 8/9 (IOM1)

void setup() {
  qwiic.begin();

  //Change SCL pull-up manually using pin_config
  am_hal_gpio_pincfg_t sclPinCfg = g_AM_BSP_GPIO_IOM1_SCL;
  sclPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_24K; // Use 24K pull-up on SCL (D8)
  pin_config(D8, sclPinCfg);

  //Change SDA pull-up manually using pin_config
  am_hal_gpio_pincfg_t sdaPinCfg = g_AM_BSP_GPIO_IOM1_SDA;
  sdaPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_24K; // Use 24K pull-up on SDA (D9)
  pin_config(D9, sdaPinCfg);
}

void loop() {
  qwiic.beginTransmission(0x55);
  qwiic.endTransmission();
}

And you should see this. Note the slow rising edges (as expected with the larger pull-up).

Interestingly, also note the slower clock speed! That’s a separate issue which has always been there. There is something in the Apollo3 hardware which monitors (probably) the clock to ensure that the clock signal is actually high for the correct period.

image

Right. OK. Excellent. pin_config works.

Now let’s look at CIPO (D6)

Using pin_config on CIPO (D6)

OpenLog Artemis has a 1.8V level shifter circuit on the SPI bus to connect it to the ICM-20948 IMU. The bus is shared with the microSD card. On the earliest SparkX hardware, we noticed that there was current leaking through the CIPO level-shifter pull-up resistor into the SD card when the Artemis was in deep sleep. This increased the stand-by current dramatically. So, we removed the external resistor and instead used the internal pull-up on the Artemis pin. We could disable the pull-up to drop the stand-by current. Perfect. Except we did that using padMode and padMode ain’t supported no more…

So, can we use pin_config? Yes, but there is an unexpected feature you need to be aware of...

To replicate:

Apollo3 v2.1.0 RedBoard Artemis ATP Connect an oscilloscope to the CIPO pin D6 Run this sketch:

#include <SPI.h>

void setup() {
  SPI.begin();

  //Change CIPO pull-up manually using pin_config
  am_hal_gpio_pincfg_t cipoPinCfg = g_AM_BSP_GPIO_IOM0_MISO;
  cipoPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_1_5K; // Use 1K5 pull-up on CIPO (D6)
  pin_config(D6, cipoPinCfg);  
}

void loop() {
}

Use a jumper wire to briefly short CIPO to GND. When you remove the jumper CIPO is pulled up quickly:

image

Now change the sketch to:

#include <SPI.h>

void setup() {
  SPI.begin();

  //Change CIPO pull-up manually using pin_config
  am_hal_gpio_pincfg_t cipoPinCfg = g_AM_BSP_GPIO_IOM0_MISO;
  cipoPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_24K; // Use 24K pull-up on CIPO (D6)
  pin_config(D6, cipoPinCfg);  
}

void loop() {
}

Note the slower rise time:

image

So, pin_config looks like it is working.

OK. Now change the code to:

#include <SPI.h>

void setup() {
  SPI.begin();

  //Change CIPO pull-up manually using pin_config
  am_hal_gpio_pincfg_t cipoPinCfg = g_AM_BSP_GPIO_IOM0_MISO;
  cipoPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_1_5K; // Use 1K5 pull-up on CIPO (D6)
  pin_config(D6, cipoPinCfg);  

  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  SPI.endTransaction();
}

void loop() {
}

And this is what we see when the code starts:

image

The pull-up on D6 is there initially, but then gets disabled by the SPI transaction. There must be something in the SPI transaction that overrides the pin configuration and disables the pull-up…

Let’s prove that by changing the code to:

#include <SPI.h>

void setup() {
  SPI.begin();

  //Change CIPO pull-up manually using pin_config
  am_hal_gpio_pincfg_t cipoPinCfg = g_AM_BSP_GPIO_IOM0_MISO;
  cipoPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_1_5K; // Use 1K5 pull-up on CIPO (D6)
  pin_config(D6, cipoPinCfg);

  delay(1);

  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  SPI.endTransaction();

  delay(1);

  //Restore the CIPO pull-up manually using pin_config
  pin_config(D6, cipoPinCfg);  
}

void loop() {
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  SPI.endTransaction();
}

And this is what we get:

image

So, the first transaction resets the pin configuration and disables the pull-up. But only the first one. Subsequent transactions do not re-disable the pull-up.

We can live with this, but it's something that anyone playing with CIPO pull-ups needs to be aware of…

For OpenLog Artemis:

paulvha commented 3 years ago

The problem is related to MBED - ownership check.

Step 1: When you call begin() it will create a new mbed-SPI environment that initializes the different variables, IOM and the pins. In my mind it SHOULD update the MBED-owner (_peripheral->owner) to "this" but it does not, that field continues to be a nullptr.

Step 2: When de first time you call SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)), in the Sparkfun library it will check whether these SPISettings are different than the previous set or default. By default the speed is zero, so it will call dev->format() and dev->frequency() in Mbed-os. These functions are in drivers/SPI.cpp.

Step 3: In Mbed-os either in dev->format() or dev->frequency() it will check whether the owner is "this". Somehow that seems to be important. The first time around it will NOT be (see step 1) and then it calls acquire() to update BOTH SPI-format and SPI-frequency, but NOT before doing a re-init with the call _init_func(this_); This will cause a complete init, same like calling begin() including resetting the pins to what is default defined in g_AM_BSP_GPIO_IOM0_MISO. After doing this, it will finally update _peripheral->owner. So now the pull-up is gone as it is not part of the default definition.

Step 4: Next time you call SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)), the setting might be same, but even if not, you are now the owner and thus a change (like the pull-up) done to the pin will NOT be reset again.

Either MBED should change (add setting ownership is step 1) or add a 24K pull up on MISO / CIPO (new terminology). Needed for some SD card readers to work correctly. Or do as PaulZC did..

regards, Paul