Configurar RTC interno com clock do LSE.

Software e Hardware para uC STM

Moderadores: andre_luis, 51

Configurar RTC interno com clock do LSE.

Mensagempor hertzing » 02 Dez 2021 15:36

Olá a todos!

Por causa da escassez de componentes no mercado, estou tendo dificuldade de comprar o componente que utilizo como RTC externo.
Assim estou querendo verificar a precisão do RTC interno do microcontrolador STM32F030R8T6, mas não consigo configurar os registradores para utilizar o LSE como clock do RTC, parece que os registradores estão protegidos mesmo após ser escrita a sequencia de desbloqueio "RTC_WPR = 0xCA; RTC_WPR = 0x53;"

Alguém teria alguma sugestão ou consideração para o código de config/inicialização abaixo?
Código: Selecionar todos
void initInternalRTC(void){
  /* (1) Write access for RTC registers */
  /* (2) Enable init phase */
  /* (3) Wait until it is allow to modify RTC register values */
  /* (4) set prescaler, 40kHz/128 => 312 Hz, 312Hz/312 => 1Hz */
  /* (5) New time in TR */
  /* (6) Disable init phase */
  /* (7) Disable write access for RTC registers */
 
  RCC_APB1ENR.PWREN = 1;      //Power interface clock enabled
 
  PWR_CR.DBP = 1;             //Disable RTC domain write protection, 1: Access to RTC enabled
  RTC_WPR = 0xCA;   /* (1) */
  RTC_WPR = 0x53;   /* (1) */

  RCC_BDCR.BDRST = 1;           //RTC domain software reset //1: Resets the entire RTC domain
  RCC_BDCR.RTCSEL0 = 1;        //Bits 9:8 RTCSEL[1:0]: RTC clock source selection
  RCC_BDCR.RTCSEL1 = 0;        //01: LSE oscillator clock used as RTC clock
  RCC_BDCR.RTCEN = 1;           //RTC clock enable

  RCC_BDCR.LSEON = 1;
  while(RCC_BDCR.LSERDY == 0){}//LSE oscillator ready
 
  //RTC_ISR |= RTC_ISR.INIT; /* (2) */
  RTC_ISR.INIT = 1;
  while ((RTC_ISR & RTC_ISR.INITF) != RTC_ISR.INITF) /* (3) */
  //while (RTC_ISR.INITF == 0) /* (3) */
  {
   /* add time out here for a robust application */
 }
  RTC_PRER = 0x007F0137; //0x007F00FF; /* (4) */
  RTC_TR = 0b00000000000100100011000000000000;//RTC_TR.B22 | Time; /* (5) */
  //RTC_ISR.INIT &=~ RTC_ISR.INIT; /* (6) */
  RTC_ISR.INIT = 0;
  RTC_WPR = 0xFE; /* (7) */
  RTC_WPR = 0x64; /* (7) */
}

Segue também a tela de configuração do microcontrolador no MikroC.
Imagem
Avatar do usuário
hertzing
Byte
 
Mensagens: 119
Registrado em: 12 Jan 2011 13:26
Localização: Blumenau / SC

Re: Configurar RTC interno com clock do LSE.

Mensagempor vtrx » 02 Dez 2021 18:36

Não vale a pena,a precisão é péssima,vai precisar bolar um ótimo algoritmo para auto ajuste,a tendência é que adiante.
Código: Selecionar todos
#include "rtc.c"
#include "rtc.h"
...
rtc.h
    typedef struct 
    { 
        u8 hour; 
        u8 minute; 
        u8 second; 
     
        u16 year; 
        u8  month; 
        u8  date; 
        u8  week; 
    }tm; 
     
    extern tm timer; 
     
    void Rtc_Init(void); 
    void Rtc_TIME_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second); 
    u8 Is_LeapYear(u16 year); 
    u32  Date_TO_Sec(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second); 
    void Rtc_TIME_AutoSet(void); 
    void Rtc_Get(void); 
    void Rtc_ALARM_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second); 
    u8 Rtc_DAY_Get(u16 year,u8 month,u8 day); 
...
rtc.c
#include "rtc.h" 
#include "stdio.h" 
 
tm timer;                   //Definir a estrutura do relógio, a função principal pode chamar diretamente esta estrutura de tempo de leitura
 
//Tabela de datas do mês da semana, tabela de abreviaturas do mês
const u8 Days_Table[12]={31,28,31,30,31,30,31,31,30,31,30,31}; 
const u8 Month_Table[12][3]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
 
//const u8* Week_Table[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; 
const u8* Week_Table[7]={"Domingo","Segunda","Terça  ","Quarta ","Quinta ","Sexta  ","Sabado "};  //21
//Folha de dados revista mensal                                                                           
u8 const _Week[12]={0,3,3,6,1,4,6,2,5,0,3,5};   
 
void Rtc_Init(void) 

    RCC->APB1ENR |= 1<<28;       //Ativar relógio PWR
    RCC->APB1ENR |= 1<<27;     // Ativar relógio BKP, calibração RTC em registradores relacionados à BKP 
    PWR->CR |= 1<<8;             //Cancelar proteção contra gravação de registro relacionada ao BKP 
    //RCC->BDCR |= 1<<16;        //Restauração suave da área de backup
    //RCC->BDCR |= ~(1<<16);      //Finalizações da reinicialização suave da área de backup   
    RCC->BDCR |= 1<<0;           //Relógio de baixa velocidade externo (LSE)   
    while(!(RCC->BDCR & 0x02));  //Esperando o relógio externo estar pronto 
    RCC->BDCR |= 1<<8;            //LSE como relógio RTC 
    RCC->BDCR |= 1<<15;            //Relógio RTC habilitado   
    while(!(RTC->CRL & (1<<5)));   //Aguarde a última operação do registro RTC para concluir
    while(!(RTC->CRL & (1<<3)));   //Aguarde a sincronização do registro RTC para concluir 
    RTC->CRH |= 0x07;               //Permitir Interrupção de Sobrecorrente [2], Interrupção de Alarme [1], Segunda Interrupção [0], Registrador CRH inferior 3 bits são válidos     
    while(!(RTC->CRL & (1<<5)));   //Aguarde a última operação do registro RTC para concluir   
    RTC->CRL |=  1<<4;             //Entre no modo de configuração
    RTC->PRLH = 0x0000;               
    RTC->PRLL = 32767;               //Definir o valor do divisor   
    //Rtc_TIME_AutoSet();               //Escreva o tempo de compilação atual no registro 
  //  Rtc_TIME_Set(2012,7,7,20,50,0);   //Ano, mês, dia, hora, minuto, segundo 
    RTC->CRL &= ~(1<<4);           //Saia do modo de configuração e comece a atualizar o registro RTC 
    while(!(RTC->CRL & (1<<5)));   //Aguarde a última operação do registro RTC para concluir
 

 
 
//Definir o tempo de início do RTC
void Rtc_TIME_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second) 

        u32 sec;     
 
        sec = Date_TO_Sec(year,month,date,hour,minute,second); 
     
        //printf("\nRtc TIME Set  Sec = %x\n",sec); 
     
        RCC->APB1ENR |= 1<<28;                         //Ativar o relógio PWR para chamada independente desta função 
        RCC->APB1ENR |= 1<<27;                         //Ativar relógio BKP 
        PWR->CR |= 1<<8;                               //Cancelar proteção contra gravação
     
        RTC-> CRL |= 1<<4;                             //Permitir configuração
         
        RTC-> CNTL = sec&0xffff;                       //Pegue os 16 bits mais baixos
        RTC-> CNTH = sec>>16;                          //Pegue os 16 bits altos
     
        RTC-> CRL &= ~(1<<4);                          //Iniciar atualização do registro RTC 
     
        while(!(RTC->CRL&(1<<5)));                     //Aguarde a conclusão da operação de registro do RTC   

 
 
 
  // Julgue se é uma função de ano bissexto
//
// Julgar método:
// Os anos comuns podem ser divididos por 4 e não podem dividir 100. (Se 2004 é o Jubileu, 1900 não é um Jubileu)
// É um ano bissexto que pode eliminar 400 no século. (Se 2000 é um ano bissexto, 1900 não é um ano bissexto)
//
// Return: 1, é um ano bissexto 0, não um ano bissexto
u8 Is_LeapYear(u16 year) 
{               
    if(year%4==0)               //Deve ser divisível por 4
    {   
        if(year%100==0)   
        {   
            if(year%400==0) 
                return 1;       //Se você terminar com 00, ainda será divisível por 400         
            else   
                return 0;     
        }else{   
            return 1;     
        } 
    }else{ 
         return 0;   
    } 

 
 
// Converter tempo para o número total de segundos para 1 de janeiro de 1970
// Bugs: O número de segundos para esta função será cerca de 20, então o valor de retorno da função é corrigido, não há problema após a correção
// Para ser otimizado
u32 Date_TO_Sec(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second) 

    u16 t; 
    u32 sec; 
 
    if(year >= 1970 && year<= 2106)  //Para determinar se é um ano legal, o tempo de RTC começa em 1970 e só pode ser representado por 32 bits em segundos, sendo o máximo apenas em torno de 2106.
    { 
        for(t= 1970 ;t<year;t++)     //Segundos acumulados para todos os anos
        { 
            if(Is_LeapYear(t))       //Julgando se é um ano bissexto
 
                sec += 31622400;   
            else     
                sec += 31536000;         
        }     
 
 
        for(t=0;t<month-1;t++)                          //Mês de segundo acumulado
        { 
            sec += (u32) Days_Table[t]*86400;             
            if(Is_LeapYear(year) && t== 1)              //Ano bissexto mais o número de segundos em um dia 
                sec += 86400;                             
     
        } 
     
        sec += (u32)(date-1)*86400;                     //Número acumulado de segundos para a data deste mês 
        sec += (u32)(hour)*3600; 
        sec += (u32)(minute)*60; 
        sec += second; 
    } 
 
    return sec-20;                                      //Corrigido por 20 segundos sem motivo aparente
 

 
 
 
// Obtém a hora atual configurar automaticamente o RTC
// pode obter tempo com base na palavra-chave do MDK
// DATA é compilada no formato: 7 de julho de 2012
// TIME para obter tempo de compilação, o formato é: 14:54:44
 
void Rtc_TIME_AutoSet() 

    u16 year,i=0,j=0; 
    u8  mon,date,sec,min,hour; 
 
    u8 *_date = __DATE__; 
    u8 *_time = __TIME__; 
 
    for(i=0;i<12;i++) 
    { 
        for(j=0;j<3;j++) 
        { 
            if(Month_Table[i][j] == _date[j]) mon = i;  //Receba o mês 
        } 
    } 
 
 
    if(_date[4]==' '){       //Obter data 
        date=_date[5]-'0';   //A operação "0" converte o tipo de caractere em um número inteiro, fazendo referência à conversão do código ASCII, por exemplo, "7" - "0" = 7
    }else{   
        date=10*(_date[4]-'0')+_date[5]-'0'; 
    } 
           
 
    year=1000*(_date[7]-'0')+100*(_date[8]-'0')+10*(_date[9]-'0')+_date[10]-'0';  //Receba o ano       
    hour=10*(_time[0]-'0')+_time[1]-'0';                                          //Ganhe horas
    min=10*(_time[3]-'0')+_time[4]-'0';                                             
    sec=10*(_time[6]-'0')+_time[7]-'0';   
     
 
    //printf("\n%d-%d-%d  %d:%d:%d\n",year,mon,date,hour,min,sec); 
 
    Rtc_TIME_Set(year,mon,date,hour,min,sec); 

 
 
// Obtém o tempo de RTC
void Rtc_Get() 

    u32 secs,days,temp,years = 1970,months = 0;       
 
    secs = RTC->CNTH;    //Leia o valor de tempo atual do RTC (total de segundos de 1970)
    secs <<= 16; 
    secs += RTC->CNTL; 
 
    //printf("\nRtc_Get  Sec = %x\n",secs); 
 
    days = secs/86400; 
    if(days > 0)         //Mais que um dia
    { 
        temp = days; 
        while(temp >= 365)     
        { 
            if(Is_LeapYear(years))              //É um ano bissexto 
            { 
                if(temp >= 366)   
                    temp -= 366;    //Ano do ano bissexto 
                else 
                    break; 
            }else{ 
                temp -= 365; 
            }             
            years++; 
        } 
         
        timer.year = years;           //Receba o ano
 
        while(days >= 28) 
        { 
            if(Is_LeapYear(years) && months ==1)       //Para julgar se é o segundo mês do ano bissexto
            { 
                if(temp >= 29)   
                    temp -= 29;   
                else 
                    break; 
            }else{ 
                if(temp >= Days_Table[months])         
                    temp -= Days_Table[months]; 
                else 
                    break; 
            } 
 
            months++;     
        } 
 
        timer.month = months+1;             //Receba meses 
        timer.date  = temp+1;               //Obter data
    } 
 
    temp = secs % 86400;                    //Obtenha os segundos restantes
    timer.hour = temp/3600;                 //Ganhe horas
    timer.minute = (temp%3600)/60;           
    timer.second = (temp%3600)%60; 
    timer.week = Rtc_DAY_Get(timer.year,timer.month,timer.date); 
 
                 

 
//Determinar o dia atual da semana                   
 
u8 Rtc_DAY_Get(u16 year,u8 month,u8 day) 
{     
    u16 temp; 
    u8 yearH,yearL; 
     
    yearH = year/100;     
    yearL = year%100;   
 
    // Se é o século 21, o número de anos mais 100 
    if( yearH > 19 ) yearL += 100; 
 
    //O número de anos passados ??é somente depois de 1900   
 
    temp = yearL+yearL/4; 
    temp = temp%7;   
    temp = temp + day + _Week[month-1]; 
 
    if( yearL%4 == 0 && month < 3 ) temp--; 
 
    return(temp%7); 

 
//Definir hora do alarme 
 
void Rtc_ALARM_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second) 

 
        u32 sec;     
 
        sec = Date_TO_Sec(year,month,date,hour,minute,second); 
 
 
        RTC-> CRL |= 1<<4;                             //Permitir configuração
 
        //while(!(RTC->CRL&(1<<5)));                       //RTOFF é 1 para gravar registros ALRL e ALRH 
         
        RTC-> ALRL = sec&0xffff;                     //Pegue os 16 bits mais baixos
        RTC-> ALRH = sec>>16;                          //Pegue os 16 bits altos
     
        RTC-> CRL &= ~(1<<4);                          //Iniciar atualização do registro RTC 
     
        while(!(RTC->CRL&(1<<5)));                     //Aguarde a conclusão da operação de registro do RTC
 

...
 void RTC_IRQHandler(void)
{
  if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
  {
   RTC_ClearITPendingBit(RTC_IT_SEC); /* Clear the RTC Second interrupt */
 TimeShow();
    /* Wait until last write operation on RTC registers has finished */
    RTC_WaitForLastTask();
    /* Reset RTC Counter when Time is 23:59:59 */
    if (RTC_GetCounter() == 0x00015180)
    {
      RTC_SetCounter(0x0);
      /* Wait until last write operation on RTC registers has finished */
      RTC_WaitForLastTask();
    }
  }
//................................
    if(RTC->CRL&0x0002)                    //Interrupção de alarme
    {
        RTC->CRL &= ~(0x0002);            //Limpar a interrupção do alarme

    }   
}
...
void TimeShow(void) 

   char buffer[38];
   char Dia[8];

    Rtc_Get();
//     snprintf(buffer,sizeof(buffer)-1,"%s %.2d/%.2d/%d - %.2d:%.2d:%.2d "
//             ,Week_Table[timer.week]
//             ,timer.date,timer.month
//             ,timer.year,timer.hour
//              ,timer.minute,timer.second);
     snprintf(Dia,8,"%s",Week_Table[timer.week]);
//     snprintf(Mes,4,"%s",(char *)Month_Table[timer.month]);   

     snprintf(buffer,sizeof(buffer)-1,"%.2d/%.2d/%d - %.2d:%.2d:%.2d "
            // ,Week_Table[timer.week]
             ,timer.date,timer.month
               ,timer.year,timer.hour
                ,timer.minute,timer.second);

    GUI_Text(64,224,(u8 *)buffer,sizeof(buffer)-16,White,Black);//rotinas do LCD
    GUI_Text(4,224,(u8 *)Dia,sizeof(Dia)-1,White,Black);//rotinas do LCD
   
}
...
main

Rtc_Init()  ;

Avatar do usuário
vtrx
Dword
 
Mensagens: 2239
Registrado em: 20 Abr 2008 21:01

Re: Configurar RTC interno com clock do LSE.

Mensagempor hertzing » 07 Dez 2021 16:08

Olá VTRX,

Agradeço pela sua contribuição,
Sobre a precisão também acredito que não terei a mesma do RTC externo, mas quero "pagar para ver" se isto não é possível contornar com o ajuste nos valores dos capacitores do cristal.

Referente a configuração, verifiquei que o STM32F0 não possui os mesmos registradores do seu código, o qual pode ser de um STM32F1, conforme a imagem abaixo
Imagem

Caso tenhas mais alguma sugestão, ficarei grato.
Avatar do usuário
hertzing
Byte
 
Mensagens: 119
Registrado em: 12 Jan 2011 13:26
Localização: Blumenau / SC

Re: Configurar RTC interno com clock do LSE.

Mensagempor vtrx » 07 Dez 2021 18:23

O código que postei é para para STM32F103.
É um código funcional para voce analisar pois foi programado no compilador Keil e esta rodando a mais de 3 anos,mas tive que ajustar a hora várias vezes neste período.
Ele sempre adianta no período de um mes.
Não creio que alterar capacitores etc seja produtivo,acho que o mais funcional seria calcular um período e fazer um pequeno atraso ou pulo na contagem.
Avatar do usuário
vtrx
Dword
 
Mensagens: 2239
Registrado em: 20 Abr 2008 21:01

Re: Configurar RTC interno com clock do LSE.

Mensagempor hertzing » 09 Dez 2021 09:42

Depois de muito tempo consegui realizar inicialização, configuração e a leitura.
Código: Selecionar todos
void initInternalRTC(void){
  /* Update LSE configuration in Backup Domain control register    */
  /* Requires to enable write access to Backup Domain of necessary */
  UART2_Write_Text("Necessário para habilitar o acesso ao Backup Domain\n");
  if(RCC_APB1ENR.PWREN == 0){
    RCC_APB1ENR.PWREN = 1;      //Power interface clock enabled
  }

  UART2_Write_Text("\nInicia Config LSE para o RTC\n");
  UART2_Write_Text("Desabilita a protecao do RTC\n");
  PWR_CR.DBP = 1;             //Disable RTC domain write protection, 1: Access to RTC enabled

  UART2_Write_Text("Reseta o domínio RTC\n");
  RCC_BDCR.BDRST = 1;
  RCC_BDCR.BDRST = 0;

  UART2_Write_Text("Define o Drive para alta capacidade\n");
  RCC_BDCR.LSEDRV0 = 1;       // Define o drive para alta capacitadade
  RCC_BDCR.LSEDRV1 = 1;       // Define o drive para alta capacitadade

  UART2_Write_Text("Verifica se a proteção está desativada\n");
  if(PWR_CR.DBP == 0){
    /* Enable write access to Backup domain */
    PWR_CR.DBP = 1;             //Disable RTC domain write protection, 1: Access to RTC enabled
    /* Wait for Backup domain Write protection disable */
    delay_ms(100);
    while(PWR_CR.DBP == 0){
      UART2_Write_Text("Protecao do Backup Domain não Habilitada\n");
      delay_ms(10);
    }
  }

  /* Set the new LSE configuration -----------------------------------------*/
  UART2_Write_Text("Define o LSE como clock do RTC\n");
  RCC_BDCR.RTCSEL0 = 1;       //Bits 9:8 RTCSEL[1:0]: RTC clock source selection
  RCC_BDCR.RTCSEL1 = 0;       //01: LSE oscillator clock used as RTC clock
  /* Check the LSE State */
  delay_ms(10);
  /* Wait till LSE is ready */
  UART2_Write_Text("Verifica se o LSE está habilitado\n");
  RCC_BDCR.LSEON = 1;
  RCC_BDCR.RTCEN = 1;         //RTC clock enable
  while(RCC_BDCR.LSERDY == 0){
     UART2_Write_Text("LSE não habilitado\n");
      delay_ms(10);
  }

  /* Desativa o power Clock */
  UART2_Write_Text("Verifica se o LSE está habilitado\n");
  if(RCC_APB1ENR.PWREN == 1){
    RCC_APB1ENR.PWREN = 0;      //Power interface clock disable
  }
}

void initCalendarRTC(char hora){
  UART2_Write_Text("Inicializa o Calendário\n");
  RTC_WPR = 0xCA;       //To unlock the write protection on all the RTC registers
  RTC_WPR = 0x53;       //To unlock the write protection on all the RTC registers
  RTC_ISR.INIT = 1;         //Enable init phase
  while(RTC_ISR.INIT == 0){ //Espera até quando a inicialização acabar
    UART2_Write_Text("Aguardando inicialização\n");
  }
  RTC_PRER = 0x007F00FF;      //set prescaler, 32,768kHz/(127+1) => 256 Hz, 256Hz/(255+1) => 1Hz
  RTC_TR.HU0 = 1;             //Muda a hora para verificação do funcionamento
  RTC_ISR.INIT = 0;           //Disable init phase
  RTC_WPR = 0xFE;             //Disable write access for RTC registers
  RTC_WPR = 0x64;             //Disable write access for RTC registers
  UART2_Write_Text("RTC ajustado com sucesso\n");
}

long tempoRTCinterno=0;
char bufferRTCint[16];
void readInitInternalRTC(void){
  if((RTC_ISR & RTC_ISR.RSF) == RTC_ISR.RSF)
  {
   tempoRTCinterno = RTC_TR;
   sprintf(bufferRTCint, "%015d\r\n", tempoRTCinterno);
   UART2_Write_Text(bufferRTCint);
   delay_ms(100);
  }
}


Sobre a precisão ainda estou cético sobre a precisão dele, mas irei testar nos próximos dias e retorno com o parecer.
Avatar do usuário
hertzing
Byte
 
Mensagens: 119
Registrado em: 12 Jan 2011 13:26
Localização: Blumenau / SC


Voltar para STMicroelectronics

Quem está online

Usuários navegando neste fórum: Nenhum usuário registrado e 1 visitante

x