8 years, 6 months ago.

How to enable Nucleo's internal lowspeed crystal?

Hello, All!

I'm going on a flight controller project with mbed. It needs accurate high speed xtal to generate accurate pwm wave(HSE). I have already fixed this problem(Nucleo uses HSE already). But another problem came to me. It does NOT need accurate RTC. I want to save the place of an external 32K xtal, using LSI instead of LSE. But the origin Nucleo Board comes with an external 32k crystal. If I want to use stm32's internal lowspeed clock source. I should just leave the osc32 in&out open. Or should I change some defination in the mbed lib?

Thank you !

2 Answers

8 years, 6 months ago.

If you use the F401 and have the crystals fitted (with the loading capacitors), they will be auto-detected on start up. The F401 has a more functional RTC where as the F103 is more basic. If you want to use the RTC properly with vBAT connected with a 3v backup cell then you will need to use this code in the rtc_api.c file (simply copy-paste the code in to replace the existing code). Otherwise your RTC will be reset on power down or nRST.

STM F401 rtc_api.c for backup RTC function

/* mbed Microcontroller Library
 *******************************************************************************
 * Copyright (c) 2014, STMicroelectronics
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of STMicroelectronics nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************
 */
#include "rtc_api.h"

#if DEVICE_RTC

#include "mbed_error.h"

static RTC_HandleTypeDef RtcHandle;

void rtc_init(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct;
    uint32_t rtc_freq = 0;

    if(RTC->ISR == 7) {     // RTC initialization and status register (RTC_ISR), cold start (with no backup domain power) RTC reset value

        RtcHandle.Instance = RTC;

        // Enable Power clock
        __PWR_CLK_ENABLE();

        // Enable access to Backup domain
        HAL_PWR_EnableBkUpAccess();

        // Reset Backup domain
        __HAL_RCC_BACKUPRESET_FORCE();
        __HAL_RCC_BACKUPRESET_RELEASE();

        // Enable LSE Oscillator
        RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
        RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_NONE; /* Mandatory, otherwise the PLL is reconfigured! */
        RCC_OscInitStruct.LSEState       = RCC_LSE_ON; /* External 32.768 kHz clock on OSC_IN/OSC_OUT */
        if (HAL_RCC_OscConfig(&RCC_OscInitStruct) == HAL_OK) {
            // Connect LSE to RTC
            __HAL_RCC_RTC_CLKPRESCALER(RCC_RTCCLKSOURCE_LSE);
            __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
            rtc_freq = LSE_VALUE;
        } else {
            // Enable LSI clock
            RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_LSE;
            RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_NONE; // Mandatory, otherwise the PLL is reconfigured!
            RCC_OscInitStruct.LSEState       = RCC_LSE_OFF;
            RCC_OscInitStruct.LSIState       = RCC_LSI_ON;
            if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
                error("RTC error: LSI clock initialization failed.");
            }
            // Connect LSI to RTC
            __HAL_RCC_RTC_CLKPRESCALER(RCC_RTCCLKSOURCE_LSI);
            __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI);
            // [TODO] This value is LSI typical value. To be measured precisely using a timer input capture
            rtc_freq = LSI_VALUE;
        }

        // Enable RTC
        __HAL_RCC_RTC_ENABLE();

        RtcHandle.Init.HourFormat     = RTC_HOURFORMAT_24;
        RtcHandle.Init.AsynchPrediv   = 127;
        RtcHandle.Init.SynchPrediv    = (rtc_freq / 128) - 1;
        RtcHandle.Init.OutPut         = RTC_OUTPUT_DISABLE;
        RtcHandle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
        RtcHandle.Init.OutPutType     = RTC_OUTPUT_TYPE_OPENDRAIN;

        if (HAL_RTC_Init(&RtcHandle) != HAL_OK) {
            error("RTC error: RTC initialization failed.");
        }
    }
}

void rtc_free(void)
{
    // Enable Power clock
    __PWR_CLK_ENABLE();

    // Enable access to Backup domain
    HAL_PWR_EnableBkUpAccess();

    // Reset Backup domain
    __HAL_RCC_BACKUPRESET_FORCE();
    __HAL_RCC_BACKUPRESET_RELEASE();

    // Disable access to Backup domain
    HAL_PWR_DisableBkUpAccess();

    // Disable LSI and LSE clocks
    RCC_OscInitTypeDef RCC_OscInitStruct;
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_LSE;
    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_NONE;
    RCC_OscInitStruct.LSIState       = RCC_LSI_OFF;
    RCC_OscInitStruct.LSEState       = RCC_LSE_OFF;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
}

int rtc_isenabled(void)
{
    if(RTC->ISR != 7) {
        return 1;
    } else {
        return 0;
    }
}

/*
 RTC Registers
   RTC_WeekDay 1=monday, 2=tuesday, ..., 7=sunday
   RTC_Month   1=january, 2=february, ..., 12=december
   RTC_Date    day of the month 1-31
   RTC_Year    year 0-99
 struct tm
   tm_sec      seconds after the minute 0-61
   tm_min      minutes after the hour 0-59
   tm_hour     hours since midnight 0-23
   tm_mday     day of the month 1-31
   tm_mon      months since January 0-11
   tm_year     years since 1900
   tm_wday     days since Sunday 0-6
   tm_yday     days since January 1 0-365
   tm_isdst    Daylight Saving Time flag
*/
time_t rtc_read(void)
{
    RTC_DateTypeDef dateStruct;
    RTC_TimeTypeDef timeStruct;
    struct tm timeinfo;

    RtcHandle.Instance = RTC;

    // Read actual date and time
    // Warning: the time must be read first!
    HAL_RTC_GetTime(&RtcHandle, &timeStruct, FORMAT_BIN);
    HAL_RTC_GetDate(&RtcHandle, &dateStruct, FORMAT_BIN);

    // Setup a tm structure based on the RTC
    timeinfo.tm_wday = dateStruct.WeekDay;
    timeinfo.tm_mon  = dateStruct.Month - 1;
    timeinfo.tm_mday = dateStruct.Date;
    timeinfo.tm_year = dateStruct.Year + 100;
    timeinfo.tm_hour = timeStruct.Hours;
    timeinfo.tm_min  = timeStruct.Minutes;
    timeinfo.tm_sec  = timeStruct.Seconds;

    // Convert to timestamp
    time_t t = mktime(&timeinfo);

    return t;
}

void rtc_write(time_t t)
{
    RTC_DateTypeDef dateStruct;
    RTC_TimeTypeDef timeStruct;

    RtcHandle.Instance = RTC;

    // Enable write access to Backup domain
    HAL_PWR_EnableBkUpAccess();

    // Convert the time into a tm
    struct tm *timeinfo = localtime(&t);

    // Fill RTC structures
    dateStruct.WeekDay        = timeinfo->tm_wday;
    dateStruct.Month          = timeinfo->tm_mon + 1;
    dateStruct.Date           = timeinfo->tm_mday;
    dateStruct.Year           = timeinfo->tm_year - 100;
    timeStruct.Hours          = timeinfo->tm_hour;
    timeStruct.Minutes        = timeinfo->tm_min;
    timeStruct.Seconds        = timeinfo->tm_sec;
    timeStruct.TimeFormat     = RTC_HOURFORMAT12_PM;
    timeStruct.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    timeStruct.StoreOperation = RTC_STOREOPERATION_RESET;

    // Change the RTC current date/time
    HAL_RTC_SetDate(&RtcHandle, &dateStruct, FORMAT_BIN);
    HAL_RTC_SetTime(&RtcHandle, &timeStruct, FORMAT_BIN);
}

#endif


You may want to change the HSE start up timeout, takes 5 seconds to nRST otherwise, to 500mS as below. But check it does lock on the crystal, you may need longer time.

stm32f4xx_hal_conf.h set HSE timeout to 500mS

#if !defined  (HSE_STARTUP_TIMEOUT)
  #define HSE_STARTUP_TIMEOUT    ((uint32_t)500)   /*!< Time out for HSE start up, in ms */
#endif /* HSE_STARTUP_TIMEOUT */

Accepted Answer

Thanks a lot, Paul. I will try this later. According to you and Erik's answers. I think the mbed lib for stm32 has some kind of automatic clock source detection function. (I saw in wiki and it said the high-speed clock has this kind of function, but it didn't mention about the lowspeed clock.) I have reserved the place for a 32K external RTC crystal and a backup battery(just a simple Li battery and a diode to charge it.) I'm now using nucleo f411, but my new project will be based on f103 since the price of a single f411 chip is 10 times of f103 chip here(It's strange that f411 is not popular here, maybe due to its price). Thank both of you. @Paul Staron @Erik Olieman

posted by Jerry Chan 03 Oct 2015

Jerry, I couldn't resolve the RTC reset issue with the F103, it uses different, 'cut down' hardware in the MCU. I found overall the F401 worked well in many areas compared with some of the other ST's. Can you use this chip instead of the F103, maybe cheaper that the F411. It may save you a lot of trouble in the long run.

posted by Paul Staron 04 Oct 2015
8 years, 6 months ago.

By default the Nucleo boards don't have this populated, and they use instead the LSI. However the only thing they use it for is their RTC (which is horribly inaccurate running at the internal oscillator). So unless you use the RTC in your flight controller it isn't even used.

Thanks,Eric. But I found that there's an external 32k crystal populated on the nucleo board. I planned to use GPS time to fix the RTC timer on every flight. So I don't need an external crystal. But I need RTC function(even inaccurate). So do you mean I should just ignore this thing and call RTC function in the program, and the mbed HAL will automatically switch to internal low-speed crystal when it failed to initialize the external 32k crystal?

posted by Jerry Chan 03 Oct 2015

Hello, I had the same question as Jerry's OP. I was able to use the above code with a slight mod to force LSI init, then was able to verify via RCC_BDCR and RCC_CSR that LSI was running. The weird thing is after reset (with power maintained), I get a Backup Domain Reset in the case if using LSI, whereas with LSE running (I have a 32K XTAL present), all is good and the RTC has not been stopped or reset. Same with Sleep (with power left ON). Any ideas on this one?

I just verified that in SystemInit() before HalInit() called, that RCC_ISR is already 0x7 in case of LSI running, where the low order 16 bits are another number like 0x27 when LSE is running. Maybe I should pull this comment, and make it a question. Thanks!

posted by Ron Grant 24 Jan 2016

I know in general that STM mbed code all reset the RTC upon initializing the RTC the first time in a program. Paul (from the answer above this one) fixed this for a bunch of STM targets, but I don't know if that might be dependent on the clock source.

posted by Erik - 25 Jan 2016