This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

MSPM0C1104: MSPM0C1104 - I2C async fast clock requests not working

Part Number: MSPM0C1104

Dear experts.

I'm developing low-power application for MSP0C1104, which should include i2c target functionality with wakeup from standby capability, and currently I'm not able to reach the correct wakeup from standby on i2c start event. My code is based on embassy project for mspm0 on Rust. Here are the main keypoints of setup:

1) The MCLK is configured to use SYSOSC directly (usemfclk=0, uselfclk=0).

2) embassy-time-driver uses TIMG8 on LFCLK to count in STANDBY. Every 2 seconds (65535 ticks on 32768Hz) it triggers interrupt to increment counter.

3) I2C is initialized with this config:

clock_source: ClockSel::BusClk,
clock_div: ClockDiv::DivBy1,
invert_sda: false,
invert_scl: false,
sda_pull: Pull::Up,
scl_pull: Pull::Up,
bus_speed: BusSpeed::Standard,

TSTART interrupt is unmasked. TWUEN bit is disabled during init according to errata ERR_04, though I tried both enabling it once and forever and enabling-disabling around wfe, doesn't affect the main issue.
Additional setups added in case:

pac::I2C0.intctl().write(|w| w.set_inteval(true));
pac::IOMUX.pincm(0).modify(|w| {
w.set_wuen(true);
w.set_wcomp(false);
});
pac::IOMUX.pincm(11).modify(|w| {
w.set_wuen(true);
w.set_wcomp(false);
});

Moreover, I assert before every listening:
assert!(!pac::I2C0.gprcm(0).clkcfg().read().blockasync());
assert!(!pac::SYSCTL.sysosccfg().read().blockasyncall());

4) I need to use STANDBY1 for my application so it's configured at the beginning:
pac::SYSCTL.mclkcfg().modify(|w| w.set_stopclkstby(true));
pac::SYSCTL
.pmodecfg()
.modify(|w| w.set_dsleep(mspm0_metapac::sysctl::vals::Dsleep::STANDBY));
cp.SCB.set_sleepdeep();
Entering standby happens through embassy executor main loop wfe() and I wait for i2c transaction here:
let command = target.listen(&mut i2c_buf).await;

5) I use additional pin for CLK_OUT monitor:
let clk_out = ClkOut::new(
p.CLK_OUT,
p.PA22,
ClkOutSource::Sysosc(Some(ClkOutDiv::Div4)),
);

Now the main problem description: I see the correct SYSOSC output before entering standby (it happens on button press), then I see bursts of SYSOSC every time TIMG8 fires it's reloading interrupt. But when I2C controller starts it's communication there is no fast clock visible on start condition. The only time i2c revives is on TIMG8 interrupts, then I see couple of SCL ticks and SDA line changes correspondingly. Basically because of these little bursts every 2 seconds I2C transaction finishes succesfully, no data is lost (providing TWUEN is set once and for all), that means I2C itself is functional, but it takes more than a minute for simple write-read. 

My main conclusion is that I2C is not able to request async fast clock, though I configured everything I could think off. I'll be happy to get any advice or possible solutions. 

Screenshot From 2026-08-19 13-56-22.png
Screenshot From 2026-08-19 13-56-52.png
Screenshot From 2026-08-19 13-57-41.png
  • Hi Ekaterina,

    The behavior you are seeing is consistent with the I2C target not generating its target-wakeup request while the device is in STANDBY1, rather than with SYSOSC or the general BLOCKASYNC controls themselves. Your observation that the transaction only advances during the periodic TIMG8 wakeups is a particularly strong indication of this: each timer wake temporarily restores the clocks, allowing the I2C state machine to make a little more progress.

    On MSPM0C1104, STANDBY1 normally stops the functional clock to I2C0. However, the device operating-mode table explicitly states that the other PD0 peripherals can generate an asynchronous fast clock request upon external activity while in STANDBY1. The I2C activity request is allowed when I2C0->GPRCM.CLKCFG.BLOCKASYNC = 0, and the request is allowed to propagate when SYSCTL->SYSOSCCFG.BLOCKASYNCALL = 0. Your assertions indicate that both of these conditions are already satisfied.

    The important point is TWUEN/SWUEN. The current MSPM0C110x errata does not recommend leaving target wakeup disabled when low-power I2C reception is required. For an application that requires I2C target reception using an asynchronous fast-clock request, the I2C_ERR_04 workaround is to enable target wakeup immediately before entering the low-power mode and disable it after exiting the low-power mode. The SDK implements this bit through DL_I2C_enableTargetWakeup() and DL_I2C_disableTargetWakeup().

    So for this application, the sequence should conceptually be:

    /* I2C target already initialized and ACTIVE */
    
    
    DL_I2C_enableTargetWakeup(I2C0);
    
    
    /* Confirm async clock requests are not blocked */
    I2C0->GPRCM.CLKCFG &= ~I2C_CLKCFG_BLOCKASYNC_MASK;
    /* SYSCTL BLOCKASYNCALL must also be clear */
    
    
    /* Clear stale I2C interrupt status before sleeping */
    /* Enable required I2C interrupt + NVIC IRQ */
    
    
    DL_SYSCTL_setPowerPolicySTANDBY1();
    
    
    __WFI();
    
    
    /* Device has returned to active mode */
    DL_I2C_disableTargetWakeup(I2C0);

    In your Embassy implementation, this sequencing is worth checking carefully because target.listen(...).await ultimately allows the executor to decide when WFE() is executed. If TWUEN is enabled and then cleared before the executor actually reaches STANDBY, or if another future causes a wake/sleep transition between those operations, the I2C could enter STANDBY1 without target wakeup enabled.

    I would therefore first remove Embassy from the equation and make a minimal DriverLib test using a simple while (1) plus __WFI(). Enable DL_I2C_enableTargetWakeup(I2C0) immediately before each __WFI() and disable it immediately after wake. If that works, then port the exact sequencing into the Embassy idle path.

    I would also verify all of the following immediately before entering STANDBY1:

    • TWUEN/SWUEN = 1
    • I2C0.GPRCM.CLKCFG.BLOCKASYNC = 0
    • SYSCTL.SYSOSCCFG.BLOCKASYNCALL = 0
    • I2C target ACTIVE = 1
    • TSTART/address/RX interrupts are enabled as needed
    • I2C0 interrupt is enabled in the NVIC
    • No stale I2C interrupt is already pending
    • I2C BUSY is not left asserted by a previous transaction

    The IOMUX WUEN bits you added on SDA/SCL should not be required for this mechanism. I2C target wakeup is generated by the I2C peripheral's PD0 logic and asynchronous clock request, rather than by configuring SDA/SCL as generic GPIO wake sources.

    Also note that the current datasheet's feature summary only explicitly advertises I2C wakeup from STOP mode, but its detailed operating-mode table allows I2C0 and other PD0 peripherals to generate asynchronous fast-clock requests in STANDBY1. Given that distinction and the I2C_ERR_04 limitation, I would validate STANDBY1 operation with the minimal TI DriverLib test before relying on the Rust implementation.

    If the minimal test still shows that TWUEN=1, both BLOCKASYNC bits are clear, and an I2C START does not generate SYSOSC activity in STANDBY1, then I would consider that unexpected behavior and we should reproduce it internally on an LP-MSPM0C1104. Your TIMG8 correlation already gives us a good way to distinguish a failed asynchronous clock request from an I2C protocol problem.

  • Dear Brian,

    Thank you for your reply. After deeper review of embassy I2C initialization I noticed it was automatically defining I2C source clock regardless of initial choice. Therefore I2C was clocked by MfClk and caused the classic I2C_ERR_03 from errata. Otherwise the async clock request now works correctly, thank you again!