Custom board based on NUCLEO_L432KC

Hello,
First of all, this is my first stab at STM32 work and Mbed, so please be easy on me. I have two boards: the NUCLEO_L432KC, and the Ladybug STM32L432. Both boards use the same chip, the STM32L432KC. The NUCLEO board is natively supported in MBed, and the code below works fine on the NUCLEO board. The Ladybug board, however, does not work. I’ll explain how after providing some more information.

I have a rather simple piece of test code that uses two UARTs (USART1 at PA9/PA10, and USART2 at PA2/PA3) and uses two GPIO pins, one to generate a pulse (PB7), and the other to works as an interrupt (PB1). In practice, I just wire a jumper between those two GPIO pins. Here’s the code:

#include "mbed.h"
#include "ThisThread.h"
#include "stats_report.h"

DigitalOut led1(LED1);
DigitalOut pulseGenerator(PB_7);
InterruptIn interruptPin(PB_1);

#define SLEEP_TIME                  100 // (msec)
#define PRINT_AFTER_N_LOOPS         20
#define MAX_RANDOMNESS              3000// (msec)

Serial usart1(PA_9,PA_10);
Serial usart2(PA_2,PA_3);

char msg1[10];
char msg2[10];
char msg3[10];

bool interruptTripped=false;

void interruptHandler(){
    interruptTripped = true;
}

uint32_t randomSleepTime = 0;

Thread pinToggleThread;
void pinToggleLoop(){
    while(true){        
        usart1.printf("Printing on USART1\n");
        usart1.printf("Flipping Pin\n");
        usart1.printf("Random Messages:\n");
        usart1.printf("msg1: Start Address = %p, End Address = %p, Size (Bytes) = %d\n",&(msg1[0]),&(msg1[1035]),sizeof(msg1));
        usart1.printf("%.*s\n",sizeof(msg1),msg1);
        usart1.printf("\nFinished Printing msg1.\n");
        usart1.printf("msg2: Start Address = %p, End Address = %p, Size (Bytes) = %d\n",&(msg2[0]),&(msg2[1035]),sizeof(msg2));
        usart1.printf("%.*s\n",sizeof(msg2),msg2);
        usart1.printf("\nFinished Printing msg2.\n");
        usart1.printf("msg2: Start Address = %p, End Address = %p, Size (Bytes) = %d\n",&(msg3[0]),&(msg3[1035]),sizeof(msg3));
        usart1.printf("%.*s\n",sizeof(msg3),msg3);
        usart1.printf("\nFinished Printing msg3.\n");

        usart1.printf("\nSleep a little\n");
        //ThisThread::sleep_for(500);

        usart1.printf("\nFlipping pulse generator.\n");
        pulseGenerator = !pulseGenerator;
        usart1.printf("\nDetermining random sleep time\n");
        //srand(time(NULL));
        usart1.printf("\nTurning rand into seconds\n");
        //randomSleepTime = ((float)rand()/(float)RAND_MAX)*MAX_RANDOMNESS;
        usart1.printf("\nSleeping\n");
        //ThisThread::sleep_for(randomSleepTime);
    }
}

// main() runs in its own thread in the OS
int main()
{
    usart2.printf("Setting char arrays\n");
    memset(msg1, 'm', sizeof(msg1));
    memset(msg2, 'k', sizeof(msg2));
    memset(msg3, 'r', sizeof(msg3));

    usart2.printf("Configure Interrupt\n");
    interruptPin.rise(&interruptHandler);
    usart2.printf("Start pinToggleThread\n");
    pinToggleThread.start(pinToggleLoop);

    usart2.printf("Print SystemReport\n");
    SystemReport sys_state( SLEEP_TIME * PRINT_AFTER_N_LOOPS /* Loop delay time in ms */);

    int count = 0;
    while (true) {
        // Blink LED and wait 0.5 seconds
        led1 = !led1;
        ThisThread::sleep_for(SLEEP_TIME);
        
        if ((0 == count) || (PRINT_AFTER_N_LOOPS == count)) {
            // Following the main thread wait, report on the current system status
            usart2.printf("Print state report\n");
            sys_state.report_state();
            usart2.printf("Printing on USART2\n");
            count = 0;
        }
        if (interruptTripped) {
            usart2.printf("Interrupt Tripped!\n");
            
            interruptTripped = false;
        }

        ++count;
    }
}

Also, here is the referenced “stats_report.h”, taken directly from an MBed example:

/* mbed Microcontroller Library
 * Copyright (c) 2018 ARM Limited
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef STATS_REPORT_H
#define STATS_REPORT_H

#include <inttypes.h>
#include "mbed.h"

/**
 *  System Reporting library. Provides runtime information on device:
 *      - CPU sleep, idle, and wake times
 *      - Heap and stack usage
 *      - Thread information
 *      - Static system information
 */
class SystemReport {
    mbed_stats_heap_t   heap_stats;
    mbed_stats_cpu_t    cpu_stats;
    mbed_stats_sys_t    sys_stats;

    mbed_stats_thread_t *thread_stats;
    uint8_t   thread_count;
    uint8_t   max_thread_count;
    uint32_t  sample_time_ms;

public:
    /**
     *  SystemReport - Sample rate in ms is required to handle the CPU percent awake logic
     */
    SystemReport(uint32_t sample_rate) : max_thread_count(8), sample_time_ms(sample_rate)
    {
        thread_stats = new mbed_stats_thread_t[max_thread_count];

        // Collect the static system information
        mbed_stats_sys_get(&sys_stats);

        printf("=============================== SYSTEM INFO  ================================\r\n");
        printf("Mbed OS Version: %" PRIu32 " \r\n", sys_stats.os_version);
        printf("CPU ID: 0x%" PRIx32 " \r\n", sys_stats.cpu_id);
        printf("Compiler ID: %d \r\n", sys_stats.compiler_id);
        printf("Compiler Version: %" PRIu32 " \r\n", sys_stats.compiler_version);

        for (int i = 0; i < MBED_MAX_MEM_REGIONS; i++) {
            if (sys_stats.ram_size[i] != 0) {
                printf("RAM%d: Start 0x%" PRIx32 " Size: 0x%" PRIx32 " \r\n", i, sys_stats.ram_start[i], sys_stats.ram_size[i]);
            }
        }
        for (int i = 0; i < MBED_MAX_MEM_REGIONS; i++) {
            if (sys_stats.rom_size[i] != 0) {
                printf("ROM%d: Start 0x%" PRIx32 " Size: 0x%" PRIx32 " \r\n", i, sys_stats.rom_start[i], sys_stats.rom_size[i]);
            }
        }
    }

    ~SystemReport(void)
    {
        free(thread_stats);
    }

    /**
     *  Report on each Mbed OS Platform stats API
     */
    void report_state(void)
    {
        printf("report_cpu_stats\n");
        report_cpu_stats();
        printf("report_heap_stats\n");
        report_heap_stats();
        printf("report_thread_stats\n");
        report_thread_stats();

        // Clear next line to separate subsequent report logs
        printf("\r\n");
    }

    /**
     *  Report CPU idle and awake time in terms of percentage
     */
    void report_cpu_stats(void)
    {
        static uint64_t prev_idle_time = 0;

        printf("================= CPU STATS =================\r\n");

        // Collect and print cpu stats
        mbed_stats_cpu_get(&cpu_stats);

        uint64_t diff = (cpu_stats.idle_time - prev_idle_time);
        uint8_t idle = (diff * 100) / (sample_time_ms * 1000);  // usec;
        uint8_t usage = 100 - ((diff * 100) / (sample_time_ms * 1000));  // usec;;
        prev_idle_time = cpu_stats.idle_time;

        printf("Idle: %d%% Usage: %d%% \r\n", idle, usage);
    }

    /**
     *  Report current heap stats. Current heap refers to the current amount of
     *  allocated heap. Max heap refers to the highest amount of heap allocated
     *  since reset.
     */
    void report_heap_stats(void)
    {
        printf("================ HEAP STATS =================\r\n");

        // Collect and print heap stats
        mbed_stats_heap_get(&heap_stats);

        printf("Current heap: %" PRIu32 "\r\n", heap_stats.current_size);
        printf("Max heap size: %" PRIu32 "\r\n", heap_stats.max_size);
    }

    /**
     *  Report active thread stats
     */
    void report_thread_stats(void)
    {
        printf("================ THREAD STATS ===============\r\n");

        // Collect and print running thread stats
        int count = mbed_stats_thread_get_each(thread_stats, max_thread_count);

        for (int i = 0; i < count; i++) {
            printf("ID: 0x%" PRIx32 " \r\n",        thread_stats[i].id);
            printf("Name: %s \r\n",               thread_stats[i].name);
            printf("State: %" PRIu32 " \r\n",       thread_stats[i].state);
            printf("Priority: %" PRIu32 " \r\n",    thread_stats[i].priority);
            printf("Stack Size: %" PRIu32 " \r\n",  thread_stats[i].stack_size);
            printf("Stack Space: %" PRIu32 " \r\n", thread_stats[i].stack_space);
        }
    }
};

#endif // STATS_REPORT_H

Also, I’m using PlatformIO for development. The custom_targets.json is below:

{
"LADYBUG": {
    "inherits": ["FAMILY_STM32"],
    "core": "Cortex-M4F",
    "extra_labels_add": [
        "STM32L4",
        "STM32L432xC",
        "STM32L432KC"
    ],
    "config": {
        "clock_source": {
            "help": "Mask value : USE_PLL_HSE_EXTC (need HW patch) | USE_PLL_HSE_XTAL (need HW patch) | USE_PLL_HSI | USE_PLL_MSI",
            "value": "USE_PLL_HSE_EXTC|USE_PLL_HSE_XTAL|USE_PLL_HSI|USE_PLL_MSI",
            "macro_name": "CLOCK_SOURCE"
        },
        "lpticker_lptim": {
            "help": "This target supports LPTIM. Set value 1 to use LPTIM for LPTICKER, or 0 to use RTC wakeup timer",
            "value": 1
        }
    },        
    "macros_add": [
        "STM32L432xx",
        "EXTRA_IDLE_STACK_REQUIRED"
    ],
    "overrides": { "lpticker_delay_ticks": 0 },
    "detect_code": ["0770"],
    "device_has_add": [
        "ANALOGOUT",
        "CRC",
        "SERIAL_ASYNCH",
        "CAN",
        "TRNG",
        "FLASH",
        "MPU"
    ],
    "release_versions": ["2", "5"],
    "device_name": "STM32L432KC",
    "bootloader_supported": true
}

}

In the file above, note that I have difference between the NUCLEO configuration, in that the NUCLEO board uses “USE_PLL_MSI” as the clock source. If I use that for the Ladybug board, the program doesn’t even start, and I get the following error:

++ MbedOS Error Info ++
Error Status: 0x80FF0100 Code: 256 Module: 255
Error Message: Fatal Run-time error
Location: 0x8006C93
Error Value: 0x0
Current Thread: �4�  Id: 0x0 Entry: 0x8002645 StackSize: 0x0 StackMem: 0x8004319 SP: 0x2000FEB4
For more info, visit: https://mbed.com/s/error?error=0x80FF0100&osver=51401&core=0x410FC241&comp=2&ver=70200&tgt=LADYBUG
-- MbedOS Error Info --
SetSysClock failed:#if MBED_CONF_TARGET_LSE_AVAILABLE

Also, here’s the mbed_app.json I’m using:

{
"target_overrides": {
    "*": {
        "platform.all-stats-enabled": true
    }
}

}

And finally the platformio.ini:

[env:ladybug]
platform = ststm32
board = ladybug
framework = mbed
build_flags = 
    -I$PROJECT_SRC_DIR/LADYBUG_TARGET
    -D PIO_FRAMEWORK_MBED_RTOS_PRESENT

Otherwise, I’ve copied the “PeripheralNames.h”, “PeripheralPins.c”, “PinNames.h”, and “system_clock.c” directly from the NUCLEO-L432KC folder and put them in my project.

As I said, this code works fine on the NUCLEO board. However, as posted, the Ladybug boards crashes.

When the code is as posted above, the Ladybug crashes in “sys_state.report_state()” function, in a subfunction called “ticker_read_us”. The error message is:

++ MbedOS Error Info ++
Error Status: 0x80FF0100 Code: 256 Module: 255
Error Message: Fatal Run-time error
Location: 0x8006599
Error Value: 0x0
Current Thread: main  Id: 0x20000F58 Entry: 0x800255F StackSize: 0x1000 StackMem: 0x20001DA0 SP: 0x20002B6C
For more info, visit: https://mbed.com/s/error?error=0x80FF0100&osver=51401&core=0x410FC241&comp=2&ver=70200&tgt=LADYBUG
-- MbedOS Error Info --
HAL_RCC_OscConfig ERROR

If I comment out the “sys_state.report_state()” line and uncomment the “srand(time(NULL))” line, it crashes with the following error:

++ MbedOS Error Info ++
Error Status: 0x80FF0100 Code: 256 Module: 255
Error Message: Fatal Run-time error
Location: 0x8006BD7
Error Value: 0x0
Current Thread: application_unnamed_thread  Id: 0x20001B24 Entry: 0x80042E1 StackSize: 0x1000 StackMem: 0x20003288 SP: 0x2000410C
For more info, visit: https://mbed.com/s/error?error=0x80FF0100&osver=51401&core=0x410FC241&comp=2&ver=70200&tgt=LADYBUG
-- MbedOS Error Info --
Cannot initialize RTC with LSE

If I comment out the “sys_state.report_state()” line and “srand(time(NULL))” line, but uncomment the “ThisThread::sleep_for(500)” line in the child thread, it crashes with the following error:

++ MbedOS Error Info ++
Error Status: 0x8001012F Code: 303 Module: 1
Error Message: Error - writing to a file in an ISR or critical section

Location: 0x8001CD5
Error Value: 0x1
Current Thread: rtx_idle  Id: 0x200010A8 Entry: 0x800246D StackSize: 0x380 StackMem: 0x200013F0 SP: 0x2000162C
For more info, visit: https://mbed.com/s/error?error=0x8001012F&osver=51401&core=0x410FC241&comp=2&ver=70200&tgt=LADYBUG
-- MbedOS Error Info --

All of this to say, that I’m clearly screwing something up. Looking at the schematics, I think they are basically equivalent, but I must be wrong. Maybe I’m missing something with the clocks? Again, all of this code in any configuration I’ve described works fine on the NUCLEO board, even with the clock sources set as I have them in custom_targets.json. I’ve provided the schematic for the Ladybug below. Any advice would be greatly appreciated. I’m kind of lost at this point.

it looks like the LSE is not working., that is the 32 kHz crystal. Also in MSI mode the LSE is used to calibrate the internal RC oscillator.
Have you tried USE_PLL_HSI? There the LSE is not used.

JojoS, it appears you are correct! Luckily, I had the foresight to buy two of these Ladybug boards. The second works as expected (after I fixed a self-inflicted wound left-over from my debugging). I’ll try to put some probes on these boards to find out what’s happening. I appreciate your help!

You’re welcome. Maybe you can measure if the Osc32 pins are shortened at the cpu or have a connection to ground. The crystal looks good, not the cheapest one.