nRF5340 with DS3231 as LXFO

Hi,

I would like to seek a guidance with proper usage of DS3231 as LFXO with nRF5340.

HW:

Custom board, DS3231 32kHz output connected to nRF's X1 with pull-up resistor. X2 floating. INT/SQW floating (we don't need sub-seconds accuracy). Both SOC and IC powered on the same 3.3 rail.

Software:

nRF Connect SDK 3.1.1.

Active-low input gpio-hog on DS3231 reset/nonitor pin.

Default devicetree settings for builtin LFXO in SOC:

&lfxo {
load-capacitors = "internal";
load-capacitance-picofarad = <7>;
};
DS3131's devicetree:
	ds3231@68 {
		compatible = "maxim,ds3231-mfd";
		reg = <0x68>;
		status = "okay";
		temp_sensor: temp_sensor {
			compatible = "maxim,ds3231-sensor";
			status = "okay";
		};
		rtc: rtc {
			compatible = "maxim,ds3231-rtc";
			status = "okay";
			isw-gpios = <&gpio0 4 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; // DUMMY
			freq-32khz-gpios = <&gpio0 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; // DUMMY
		};
	};
During AI inspection of my code, LLM accused me of heresy because I just happily configured app to use external osclllator "as-is" thinking it will just work. Is that correct?
App's config:
CONFIG_CLOCK_CONTROL_NRF_K32SRC_XTAL=y
CONFIG_CLOCK_CONTROL_NRF_K32SRC_20PPM=y
Despite that, saving system time to RTC (after time sync over BLE) and restoring it from RTC (on boot) work fine, because internal RCs are actually used for all of this?
(and I only get some drift)
How should I properly leverage externally connected (to X1) 32kHz clock? While I don't need sub-seconds accuracy, I would like system time to be as accurate as possible with that HW setup (even though device most likely will not be powered-on continuously for days).

The 32.768 kHz crystal oscillator (LFXO) is designed to work with external sources.

The following external sources are supported:
  • A low swing clock. The signal should be applied to the XL1 pin with the XL2 pin grounded. Set OSCILLATORS.XOSC32KI.BYPASS=Disabled.
  • A rail-to-rail clock. The signal should be applied to the XL1 pin with the XL2 pin left unconnected. Set OSCILLATORS.XOSC32KI.BYPASS=Enabled.
So I should set it to bypass=enabled.
Question is when to do it safely?
I suppose I can keep all bootloaders (on APP and NET) using internal LFXO (RC) as I don't need accurate timestamps there and internal HFXO is accurate enough for all USB (in ex. serial recovery) operations in bootloader:
CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y
So it leaves setup of primary app on APP core and ipc_radio on NET core.
Do they both need to use the same LFXO (in ex both RC or both external) or they just need to be independently stable? (ipc_radio for accurate timings and app for my stable system time needs)?
I've read somewhere that LXFO has to be configured (in my case to bypass mode) before HFXO is started.
 Clock Control Configuration Confirmation suggest some way to do it.
How can I inject that init code especially in ipc_radio, without copying sources?
Can I use BOARD_EARLY_INIT_HOOK or something similar?
And the most important, how should devicetree and config look like for RC bypass to work?
I suppose I need
&lfxo {
    load-capacitors = "external";
};
(is status = "okay" needed or driver reads this property regardless because it has to?)
and enable external oscillator even when we are in fact not using oscillator:
CLOCK_CONTROL_NRF_K32SRC_XTAL=y
Can you comment?
Thanks in advance!
Parents
  • Hello,

    The clock driver does not support external clock sources, but you should be able to work around this by setting the bypass register early on boot before the clock driver init as Bendik suggested in the post you linked (Clock Control Configuration Confirmation). 

    I suggest you start with something simple like the "Hello World" sample first to verify that you are able to run it with your external clock source (CLOCK_CONTROL_NRF_K32SRC_XTAL must be set to =y). Since the system timer used by Zephyr relies on this clock source, the program will not reach main() unless the LF clock was started (this assumes the default CONFIG_SYSTEM_CLOCK_WAIT_FOR_STABILITY is selected).

    how should devicetree and config look like for RC bypass to work?
    I suppose I need

    Yes, you should select "external" for the load caps.

    Best regards,

    Vidar 

Reply
  • Hello,

    The clock driver does not support external clock sources, but you should be able to work around this by setting the bypass register early on boot before the clock driver init as Bendik suggested in the post you linked (Clock Control Configuration Confirmation). 

    I suggest you start with something simple like the "Hello World" sample first to verify that you are able to run it with your external clock source (CLOCK_CONTROL_NRF_K32SRC_XTAL must be set to =y). Since the system timer used by Zephyr relies on this clock source, the program will not reach main() unless the LF clock was started (this assumes the default CONFIG_SYSTEM_CLOCK_WAIT_FOR_STABILITY is selected).

    how should devicetree and config look like for RC bypass to work?
    I suppose I need

    Yes, you should select "external" for the load caps.

    Best regards,

    Vidar 

Children
  • It somewhat works but also doesn't.

    I want my bootloaders to use RC (more compatible, and there are no timestamps printed there so I don't care), so I left mcuboot and b0n to use internal load capacitor and RC clock in config. They also never touch my DS3231 RTC over I2C.

    I am only setting BYPASS=1 in SYS_INIT and selecting XTAL of my main app on App core. I assumed ipc_radio will get it anyway, per [1]:

    If two instances of the LFCLK control system request different LFCLK sources, the power and clock subsystem will secure that the most accurate of the requested LFCLK sources is selected.

    My main app on boot reads RTC from DS3221 over I2C and immediately calls clock_settime(CLOCK_REALTIME, ..).

    And when I print clock control registers in my main app, I get:

    BYPASS=1 LFCLKSRC=2 LFCLKRUN=1 LFCLKSRCCOPY=2 LFCLKSTAT={SRC=2 ALWAYSRUNNING=0 STATE=1}

    So everything looks fine on app core, after a week system time drift is within 1s.

    But BLE (that is running on ipc_radio) silently stops working or at least it appeas so as I cannot connect anymore with paired BLE client. I don't have debug logs from Net core unfortunately so can only guess so far.

    When I trigger some BLE HCI command (in ex by triggering pairing, so reconfiguration of sending advertisement packets with different allowlist), app core crashes with:

    2026-05-31T16:00:46.184Z <dbg> [sysworkq] main: stop_advertising_work_handler:
    ASSERTION FAIL [err == 0] @ WEST_TOPDIR/external/zephyr/subsys/bluetooth/host/hci_core.c:504
    Controller unresponsive, command opcode 0x200a timeout with err -11

    Would that be because Net core uses different LFXO (RC) and somehow got de-synchronized with App over time?

    I did not configure ipc_radio to request LFXO and if cores got indeed desynchronized then maybe:

    "If two instances of the LFCLK control system request different LFCLK sources, the power and clock subsystem will secure that the most accurate of the requested LFCLK sources is selected." doesn't work?

    Or there is race condition because maybe ipc_radio is booted first (because b0n booted it sooner than mcuboot booted main app), ipc_radio requested LFCLK, got RC (because higher accuracy LFXO was not available yet), then main app booted and requested LCFLK, got LFXO, but it happened too late for ipc_radio to pick it up? I wil test this but would like to know how it's supposed to work because reference manual is not fully clear on this, in ex whether cores can run on different low frequency clocks.

    Also, what is expected voltage level on signal on X1 vs VDD in case of rail-to-rail external LFXO and expected signal shape for  X1 in such configurtation on nRF5340 ?

    My nRF5340 runs on VDD=3.3, 100k pull-up resistor on X1 brings down external 32KHz down to 1,64V. It is also a pulse, not square. Does it need to be square? Oscilloscope output below:

    LLM in typical overly confident manner says that resistor is too strong and I should get it closer to 3.3V and it's root cause for BLE not working. But it sounds like nonsense to me, if I indeed have "significant parasitic capacitance" on X1, and external LFXO is not interpreted correctly by nRF, it would fail to keep system time stable on App core all the same  - I think - but that one appears to work fine.

    Thanks in advance!

    1. docs.nordicsemi.com/.../clock.html

  • I would expect the peaks to be at VDD. Are you able to swap out the 100K resistor with a smaller value (10 k or 4.7 kOhm) and see if the signal improves with a stronger pull up resistor? It also looks like the oscilloscope's sample rate may be too low to accurately capture the waveform.

    reavertm said:
    "If two instances of the LFCLK control system request different LFCLK sources, the power and clock subsystem will secure that the most accurate of the requested LFCLK sources is selected." doesn't work?

    Yes, LFXO will be used by the system when active. But you should avoid selecting different clock sources in your project. One problem with selecting the LFRC source is that this will enable periodic clock calibration against HFXO.

Related