Skip to main content

AN02: PCB bringup guidelines

Summary

This document aims to describe the various steps involved in bringing up and testing a custom nPZero based PCB.

The screenshots and results in this document are based on a custom board using the nPZero G1S and the Nordic Semiconductor nRF54L15, but the basic steps should work regardless of the MCU used.

Basic tests

The following steps will test basic board functionality to ensure that there are no shortcuts or similar, that the MCU is responding and accessible for programming, and that the UART interface is operational.

Supply test

  1. Start by applying power to the board supply. Use a power supply with current limiter, to avoid frying the board in case of a shortcut. Set the current limiter to 20mA or less, if possible.

  2. Check the amount of current flowing into the board, and verify that it looks normal. PCB supply test

    danger

    If the test fails, meaning the current is unexpectedly low or high, it points to a PCB issue:

    • Check that all components are correctly assembled
    • Check for soldering issues
    • Recheck the design files to ensure there are no errors in the design

Programmer test

  1. Connect a programmer to the MCU and verify that the programmer can access it. PCB Segger test

    When using Nordic Semiconductor based hosts the nrfutil command line utility can be used. For other MCU architectures use a comparable method:

    C:\Users\USER>nrfutil device device-info
    serial_number: 000053004491
    deviceFamily: NRF54L_FAMILY
    deviceName: nRF54L15
    deviceVersion: NRF54L15_xxAA_REV1
    jlinkObFirmwareVersion: J-Link V13 compiled Oct 15 2025 16:56:43

    The output from the nrfutil command shows that the MCU core is powered up and responding to the programmer, allowing the MCU to be programmed.

    danger

    If the test fails it points to an issue with the MCU, or the debug connection to the MCU

    • If possible check the voltage level on the VDD line of the MCU, to ensure it has power.
    • Double check that the programmer is correctly connected to the debug interface of the MCU.
    • Are decoupling/bypass capacitors and required crystals correctly connected to the MCU?

UART test

  1. Open the IDE of choice for your MCU, and prepare the standard nPZero example as found on https://github.com/nanopowersemi.

    Make sure to configure the MCU according to the pinout and components used on the custom PCB.

  2. Just after all the peripherals are initialized, add a while loop that simply prints a text to the log (UART) in order to verify that log output is working correctly.

    // Make sure to run all the normal init functions,
    // including npz_hal_init(),
    // before running the snippet below:

    // Add this snippet to test the UART output
    while (1) {
    static int counter = 0;
    printk("Testing uart. Counter = %i\r\n", counter++);
    k_msleep(500);
    }
  3. Connect an FTDI adapter to your board, to receive the UART output. Connect to the FTDI adapter using the same settings as the MCU.

    By default the example will use the following settings:

    BaudrateParityStop bitsHW flow control
    115200Off1Off
  4. Verify that the UART log is coming through: Log output verification

    danger

    If nothing is showing up on the UART interface check the following:

    • Is the FTDI adapter correctly connected? Typically TX on the PCB should be connected to RX on the FTDI adapter.
    • Is the terminal configured with the right UART settings, according to the settings of the MCU?
    • Is the MCU firmware configured correctly, to ensure the UART is enabled on the right pins?
    • Connect the scope to the UART TX line and check if it is active. When idle the line should be high (=VDD), pulsing low when there is UART activity. UART working
    • Double check that the MCU firmware is correctly configured, and that the flashing operation is successful. In some cases the board most be explicitly reset after flashing.

nPZero tests

The following steps will verify that the communication between the MCU and the nPZero is functional, and that the nPZero is responding to register read/write commands as expected.

nPZero register read test

  1. In order to verify that the communication between the MCU and the nPZero is working, attempt to read the ID register of the nPZero:

    Replace the earlier code snippet for testing the UART with the following:

    // Make sure to run all the normal init functions,
    // including npz_hal_init(),
    // before running the snippet below:

    uint8_t npz_id;
    npz_status_e error = npz_read_ID(&npz_id);
    if(error != OK) printf("I2C bus error!\r\n");
    printf("NPZ ID: 0x%.2x\r\n", (int)npz_id);
    while (1);
  2. Verify that the code snippet runs as expected.

    The UART output should look like this:

    *** Booting nRF Connect SDK v3.1.1-e2a97fe2578a ***
    *** Using Zephyr OS v4.1.99-ff8f0c579eeb ***
    nPZero Host is active.........
    NPZ ID: 0x60

    A trace of the I2C interface should look like this: I2C comms reading ID register

nPZero idle test

  1. The next step is to verify whether or not the nPZero is able to power cycle the MCU. To do so replace the earlier code snippet with the one below.

    warning

    Make sure to change the host power switch mode depending on your PCB configuration, as explained in the code snippet.

    // Make sure to run all the normal init functions

    // Set a global timeout value, which will cause the nPZero to wakeup the system at a regular interval
    npz_register_tout_s global_timeout = {.tout_l = 40, .tout_h = 0}; // 40 * 100ms = 4 second wakeup
    npz_status_e error = npz_write_TOUT(global_timeout);
    if(error != OK) printf("I2C bus error during TOUT write!\r\n");

    // Configure the host power switch mode, based on the PCB schematic.
    // If the host MCU supply is connected directly to the nPZero, use POWER_SWITCH_MODE_STANDARD.
    // If an external power switch is used the mode should be POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH or POWER_SWITCH_MODE_LOGIC_OUTPUT_LOW, depending on the polarity of the switch control signal.
    npz_register_pswctl_s pswctl = {.pswh_mode = POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH};
    error = npz_write_PSWCTL(pswctl);
    if(error != OK) printf("I2C bus error during PSWCTL write!\r\n");

    // Attempt to enter idle mode, by writing 0xFF to the idle reset register of the nPZero
    printf("Attempt to enter idle mode...\r\n");
    error = npz_write_SLEEP_RST(0xFF);
    if(error != OK) printf("I2C bus error during IDLE_RST write!\r\n");

    // Enter an infinite loop. If the enter idle command is successful this loop should not run, since the MCU will be disconnected from power
    while(1) {
    k_msleep(1000);
    printf("Failed to enter sleep!\r\n");
    }
  2. Verify that the device is able to enter sleep, by checking the UART log and the status of the I2C signals and the host supply line:

    The log output should show the MCU booting once every 4 seconds (approximately):

    *** Booting nRF Connect SDK v3.1.1-e2a97fe2578a ***
    *** Using Zephyr OS v4.1.99-ff8f0c579eeb ***
    nPZero Host is active.........
    Attempt to enter idle mode...
    *** Booting nRF Connect SDK v3.1.1-e2a97fe2578a ***
    *** Using Zephyr OS v4.1.99-ff8f0c579eeb ***
    nPZero Host is active.........
    Attempt to enter idle mode...
    *** Booting nRF Connect SDK v3.1.1-e2a97fe2578a ***
    *** Using Zephyr OS v4.1.99-ff8f0c579eeb ***
    nPZero Host is active.........
    Attempt to enter idle mode...

    The scope trace should look something like the screenshots below, showing the MCU supply enabled repeatedly after a 4 second delay:

    Zoom in on one power cycleTotal sleep cycle
    Sleep cycle close upSleep cycle close
    danger

    If the power cycling is not working properly check the following:

    • Check the connection between the SW_HP pin on the nPZero and the supply line of the MCU. Are there any switches in between which might not be connected correctly?
    • Double check the power switch configuration in the code snippet.

nPZero peripheral power switch test

  1. Verify that the peripheral power switches are functional, allowing the nPZero to supply connected peripherals.

    Start by adding the following function to main.c, above the main() function:

    void npz_peripheral_dummy_config(npz_psw_e psw, npz_power_switch_mode_e pwr_sw_mode)
    {
    // Configure the peripheral in periodic polling mode,
    // and use the power switch mode provided in the function argument
    npz_register_cfgp_s p_cfgp = {.pwmod = POWER_MODE_PERIODIC, .pswmod = pwr_sw_mode};
    // Peripheral polling approximately every 1 second
    npz_register_perp_s p_perp = {.perp_l = 10, .perp_h = 0};
    npz_register_twtp_s p_twtp = {.twtp = 78};
    npz_register_tcfgp_s p_tcfgp = {.tinit_en = 1, .twt_en = 1};
    npz_register_addrp_s p_addrp = {.spi_en = 1};
    int error = npz_write_CFGP(psw, p_cfgp);
    error |= npz_write_PERP(psw, p_perp);
    error |= npz_write_TWTP(psw, p_twtp);
    error |= npz_write_TCFGP(psw, p_tcfgp);
    error |= npz_write_ADDRP(psw, p_addrp);
    if(error != OK) printf("I2C bus error during peripheral %i configuration!\r\n", psw);
    }

    Replace the code snippet in the main function with the one below. Ensure that the npz_peripheral_dummy_config(..) function is called for each peripheral that is used on the PCB, and ensure the power switch mode is correctly set for each peripheral.

    // Make sure to run all the normal init functions

    // Set a global timeout value, which will cause the nPZero to wakeup the system at a regular interval
    npz_register_tout_s global_timeout = {.tout_l = 40, .tout_h = 0}; // 40 * 100ms = 4 second wakeup
    npz_status_e error = npz_write_TOUT(global_timeout);
    if(error != OK) printf("I2C bus error during TOUT write!\r\n");

    // Configure the host power switch mode, based on the PCB schematic.
    // If the host MCU supply is connected directly to the nPZero, use POWER_SWITCH_MODE_STANDARD.
    // If an external power switch is used the mode should be POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH or POWER_SWITCH_MODE_LOGIC_OUTPUT_LOW, depending on the polarity of the switch control signal.
    npz_register_pswctl_s pswctl = {.pswh_mode = POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH};
    error = npz_write_PSWCTL(pswctl);
    if(error != OK) printf("I2C bus error during PSWCTL write!\r\n");

    // Call the npz_peripheral_dummy_config(..) function for each power switch that is utilized on the PCB.
    // Like in earlier steps, ensure the power switch mode is correctly configured according to the PCB schematic.
    npz_peripheral_dummy_config(PSW_LP1, POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH);
    npz_peripheral_dummy_config(PSW_LP2, POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH);
    npz_peripheral_dummy_config(PSW_LP3, POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH);
    npz_peripheral_dummy_config(PSW_LP4, POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH);

    // Attempt to enter idle mode, by writing 0xFF to the idle reset register of the nPZero
    printf("Attempt to enter idle mode...\r\n");
    error = npz_write_SLEEP_RST(0xFF);
    if(error != OK) printf("I2C bus error during IDLE_RST write!\r\n");

    // Enter an infinite loop. If the enter idle command is successful this loop should not run, since the MCU will be disconnected from power
    while(1) {
    k_msleep(1000);
    printf("Failed to enter sleep!\r\n");
    }

    The scope trace should look something like the screenshot below, showing a ~50ms pulse on each power switch at roughly 1 second intervals: Peripheral power switch cycling

    danger

    If the power cycling is not working properly check the following:

    • Check the connection between the SW_LPn pin on the nPZero and the supply line of the peripheral. Are there any switches in between which might not be connected correctly?
    • Double check the peripheral index and the power switch configuration in the call to npz_peripheral_dummy_config(..)

Peripheral tests

I2C register read test

  1. To verify that I2C peripherals are connected properly and can be interfaced by the nPZero it is possible to configure the nPZero to read one of the configuration registers of the peripheral. Many I2C devices contain static identification or 'who am I' registers that will always return a known fixed value, and these registers are useful to verify that the device is properly connected and responding to I2C commands.

    To run this test start by adding the below function to the main.c file, above the main() function:

    void npz_peripheral_i2c_reg_read_test(npz_psw_e psw, npz_power_switch_mode_e pwr_sw_mode, uint8_t sensor_i2c_addr, uint8_t reg_addr)
    {
    // Similar to earlier steps, set the power switch mode based on the PCB schematic,
    // and how the SW_LPn pins from the nPZero are connected to the supply of sensors.
    npz_register_cfgp_s p_cfgp = {.pwmod = POWER_MODE_PERIODIC, .pswmod = pwr_sw_mode};
    // Peripheral polling approximately every 1 second
    npz_register_perp_s p_perp = {.perp_l = 10, .perp_h = 0};
    npz_register_twtp_s p_twtp = {.twtp = 78};
    npz_register_tcfgp_s p_tcfgp = {.tinit_en = 1, .twt_en = 1};
    npz_register_addrp_s p_addrp = {.spi_en = 0, .addrp = sensor_i2c_addr};
    npz_register_rregp_s p_rregp = {.rregp = reg_addr};
    npz_register_modp_s p_modp = {.seqrw = 1, .swprreg = 1};
    int error = npz_write_CFGP(psw, p_cfgp);
    error |= npz_write_PERP(psw, p_perp);
    error |= npz_write_TWTP(psw, p_twtp);
    error |= npz_write_TCFGP(psw, p_tcfgp);
    error |= npz_write_ADDRP(psw, p_addrp);
    error |= npz_write_RREGP(psw, p_rregp);
    error |= npz_write_MODP(psw, p_modp);
    if(error != OK) printf("I2C bus error during peripheral %i configuration!\r\n", psw);
    }
  2. Change the code snippet in main() using the snippet below. Update the I2C address and the register address according to the peripherals used on the custom PCB, and make sure the peripheral_test_index is updated accordingly.

    // Make sure to run all the normal init functions

    // Make sure to update the peripheral index based on which peripheral is being tested.
    npz_psw_e peripheral_test_index = PSW_LP4;

    // Read the last read value from the peripheral.
    npz_register_valp_s per_value;
    npz_read_VALP(peripheral_test_index, &per_value);
    printf("Peripheral register value: 0x%.2x%.2x\r\n", per_value.valp_h, per_value.valp_l);

    // Set a global timeout value, which will cause the nPZero to wakeup the system at a regular interval
    npz_register_tout_s global_timeout = {.tout_l = 40, .tout_h = 0}; // 40 * 100ms = 4 second wakeup
    npz_status_e error = npz_write_TOUT(global_timeout);
    if(error != OK) printf("I2C bus error during TOUT write!\r\n");

    // Configure the host power switch mode, based on the PCB schematic.
    // If the host MCU supply is connected directly to the nPZero, use POWER_SWITCH_MODE_STANDARD.
    // If an external power switch is used the mode should be POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH or POWER_SWITCH_MODE_LOGIC_OUTPUT_LOW, depending on the polarity of the switch control signal.
    npz_register_pswctl_s pswctl = {.pswh_mode = POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH};
    error = npz_write_PSWCTL(pswctl);
    if(error != OK) printf("I2C bus error during PSWCTL write!\r\n");

    // Run the I2C peripheral read test for the AS6212 temperature sensor included in the nPZero G1S DevKit.
    // The sensor uses I2C address 0x49, and the configuration register at register address 0x01 can be used
    // in the absence of other ID registers
    npz_peripheral_i2c_reg_read_test(peripheral_test_index, POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH, 0x49, 0x01);

    // Attempt to enter idle mode, by writing 0xFF to the idle reset register of the nPZero
    printf("Attempt to enter idle mode...\r\n");
    error = npz_write_SLEEP_RST(0xFF);
    if(error != OK) printf("I2C bus error during IDLE_RST write!\r\n");

    // Enter an infinite loop. If the enter idle command is successful this loop should not run, since the MCU will be disconnected from power
    while(1) {
    k_msleep(1000);
    printf("Failed to enter sleep!\r\n");
    }

    After letting the code run for a couple of wakeup cycles the log output should look something like this:

    nPZero Host is active ......
    Peripheral register value: 0x0000
    Attempt to enter idle mode...

    nPZero Host is active ......
    Peripheral register value: 0x40a0
    Attempt to enter idle mode...

    The I2C trace should look something like this: I2C register read

    The I2C address is acknowledged, meaning the peripheral is responding, and the returned value of register 0x01 is 0x40A0 (binary b0100000010100000), which is the default value of the AS6212 config register according to the datasheet.

    danger

    If the expected register value is not returned, check the following:

    • Ensure the right peripheral index is used, depending on which power switch index the peripheral is connected to.
    • In case the I2C address is not acknowledged, double check that the I2C address is configured correctly.
    • Check the analog values on the scope to verify that the I2C signals voltages look OK. If not it could be an issue with the I2C pull up resistors.

SPI register read test

  1. A test similar to the I2C read test can be implemented for SPI based peripherals, where a register in the peripheral can be read out to verify that the peripheral is properly powered up and the communication interface is working. The nPZero allows a series of bytes to be sent to the peripheral over SPI before one or two empty bytes are clocked out, and whatever data is returned from the peripheral on the MISO line during those bytes will be stored as the peripheral value.

    Start by adding the below function to the main.c file, above the main() function:

    void npz_peripheral_spi_reg_read_test(npz_psw_e psw, npz_power_switch_mode_e pwr_sw_mode, npz_spimod_e spi_mode, uint8_t reg_addr)
    {
    // Similar to earlier steps, set the power switch mode based on the PCB schematic,
    // and how the SW_LPn pins from the nPZero are connected to the supply of sensors.
    npz_register_cfgp_s p_cfgp = {.pwmod = POWER_MODE_PERIODIC, .pswmod = pwr_sw_mode};
    // Peripheral polling approximately every 1 second
    npz_register_perp_s p_perp = {.perp_l = 10, .perp_h = 0};
    npz_register_twtp_s p_twtp = {.twtp = 78};
    npz_register_tcfgp_s p_tcfgp = {.tinit_en = 1, .twt_en = 1};
    npz_register_addrp_s p_addrp = {.spi_en = 1, .addrp = 1};
    npz_register_modp_s p_modp = {.seqrw = 1, .dtype = DATA_TYPE_UINT8, .spimod = spi_mode};
    int error = npz_write_CFGP(psw, p_cfgp);
    error |= npz_write_PERP(psw, p_perp);
    error |= npz_write_TWTP(psw, p_twtp);
    error |= npz_write_TCFGP(psw, p_tcfgp);
    error |= npz_write_ADDRP(psw, p_addrp);
    error |= npz_write_MODP(psw, p_modp);
    error |= npz_write_SRAM(0x80, reg_addr);
    if(error != OK) printf("I2C bus error during peripheral %i configuration!\r\n", psw);
    }
  2. Change the code snippet in main() using the snippet below. Update the register address according to the peripherals used on the custom PCB, and make sure the peripheral_test_index is updated accordingly. This particular example will read the WHO_AM_I register in the LIS2DW sensor, which should return the value 0x44:

    // Make sure to run all the normal init functions

    // Make sure to update the peripheral index based on which peripheral is being tested.
    npz_psw_e peripheral_test_index = PSW_LP3;

    // Read the last read value from the peripheral.
    npz_register_valp_s per_value;
    npz_read_VALP(peripheral_test_index, &per_value);
    printf("Peripheral register value: 0x%.2x%.2x\r\n", per_value.valp_h, per_value.valp_l);

    // Set a global timeout value, which will cause the nPZero to wakeup the system at a regular interval
    npz_register_tout_s global_timeout = {.tout_l = 40, .tout_h = 0}; // 40 * 100ms = 4 second wakeup
    npz_status_e error = npz_write_TOUT(global_timeout);
    if(error != OK) printf("I2C bus error during TOUT write!\r\n");

    // Configure the host power switch mode, based on the PCB schematic.
    // If the host MCU supply is connected directly to the nPZero, use POWER_SWITCH_MODE_STANDARD.
    // If an external power switch is used the mode should be POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH or POWER_SWITCH_MODE_LOGIC_OUTPUT_LOW, depending on the polarity of the switch control signal.
    npz_register_pswctl_s pswctl = {.pswh_mode = POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH};
    error = npz_write_PSWCTL(pswctl);
    if(error != OK) printf("I2C bus error during PSWCTL write!\r\n");

    // Run the SPI peripheral read test for the LIS2DW accelerometer included in the nPZero G1S DevKit.
    // The LIS2DW has a static WHO_AM_I register at address 0x0F, which can be read by clocking out 0x8F on MOSI
    npz_peripheral_spi_reg_read_test(peripheral_test_index, POWER_SWITCH_MODE_LOGIC_OUTPUT_HIGH, SPIMOD_SPI_MODE_0, 0x8F);

    // Attempt to enter idle mode, by writing 0xFF to the idle reset register of the nPZero
    printf("Attempt to enter idle mode...\r\n");
    error = npz_write_SLEEP_RST(0xFF);
    if(error != OK) printf("I2C bus error during IDLE_RST write!\r\n");

    // Enter an infinite loop. If the enter idle command is successful this loop should not run, since the MCU will be disconnected from power
    while(1) {
    k_msleep(1000);
    printf("Failed to enter sleep!\r\n");
    }

    The log output should look something like below:

    nPZero Host is active ......
    Peripheral register value: 0x0000
    Attempt to enter idle mode...

    nPZero Host is active ......
    Peripheral register value: 0x0044
    Attempt to enter idle mode...
    danger

    If the expected register value is not returned, check the following:

    • Ensure the right peripheral index is used, depending on which power switch index the peripheral is connected to.
    • Double check the SPI mode configuration, to ensure it is compliant with that used by the peripheral.
    • Check the analog values on the scope to verify that the SPI signals voltages look OK.