STM32 实时时钟(RTC)的配置和时钟切换
目前硬件核心板存在一个问题,外部32.768KHz的晶振经常无法起振,硬件工程师将晶振两端的1MΩ电阻去掉,电容从10pF换成6pF后大部分能起振了,个别的还是不行。目前是外部LSE无法起振的情况下直接死机,为了让上位机知道核心板是否在正常工作,要保证外部晶振无法起振的情况下切换到内部晶振,系统其他功能还能正常工作。
先配置下外部时钟:
1、RCC选择外部晶振
2、Timers的RTC栏里,勾选 Activate两个,RTC OUT 选择 Disable
3、参数设置,日期格式采用二进制方式:Binary data format,其他日历时间自行设置
4、时钟树配置:选择外部晶振:LSE
通过以上操作就完成了RTC的配置。
在modbus校时指令和应用程序中对时间和日期进行更新,两个应用中都是对时间和日期的写操作,因此需要一个互斥量保持数据完整性。
modbus校时线程代码如下:
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
......
extern rt_mutex_t set_time_mutex;
rt_mutex_take(set_time_mutex, RT_WAITING_FOREVER);
......
case 0x08: // setyear // 判断len, 如果len大于
break;
case 0x09: // setmonth
break;
case 0x0A: // setday
break;
case 0x0B: // sethour
break;
case 0x0C: // setminute
break;
case 0x0D: // setsecond
break;
case 0x0E: // setmsecond
sTime.Hours = armsyspara.settime.sethour;
sTime.Minutes = armsyspara.settime.setminu;
sTime.Seconds = armsyspara.settime.setsec;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
{
// Error_Handler(); // add alrm word
}
//DateToUpdate.WeekDay = RTC_WEEKDAY_TUESDAY;
DateToUpdate.Year = armsyspara.settime.setyear;
DateToUpdate.Month = armsyspara.settime.setmonth;
DateToUpdate.Date = armsyspara.settime.setday;
if(HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
{
// Error_Handler(); // add alrm word
}
LOG_I("Set Time: %02d/%02d/%02d %02d:%02d:%02d",2000 + DateToUpdate.Year, DateToUpdate.Month, DateToUpdate.Date,
sTime.Hours, sTime.Minutes, sTime.Seconds); // Display date and time Format: yy/mm/dd hh:mm:ss
break; // 这一块一起处理
......
rt_mutex_release(set_time_mutex);
时间更新线程如下:
RTC_DateTypeDef GetData; //获取日期结构体
RTC_TimeTypeDef GetTime; //获取时间结构体
set_time_mutex = rt_mutex_create("set_time_mutex", RT_IPC_FLAG_FIFO); // create set time mutex
while (1)
{
HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN); // Get the RTC current Time
HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN); // Get the RTC current Date
rt_mutex_take(set_time_mutex, RT_WAITING_FOREVER); // refresh set time
armsyspara.settime.setyear = GetData.Year;
armsyspara.settime.setmonth = GetData.Month;
armsyspara.settime.setday = GetData.Date;
armsyspara.settime.sethour = GetTime.Hours;
armsyspara.settime.setminu = GetTime.Minutes;
armsyspara.settime.setsec = GetTime.Seconds;
rt_mutex_release(set_time_mutex);
rt_thread_mdelay(1000);
}
解决最后的问题。仿真的时候可以看到,外部晶振没有起振的情况下死机而无法正常运行,为了解决此问题,要修改超时的库函数,并且切换到LSI。
void SystemClock_Config(void)改为:
void SystemClock_Config(void)
{
uint8_t LSE_Status = HAL_OK;
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
if(HAL_RCC_OscConfig(&RCC_OscInitStruct) == HAL_TIMEOUT)
{
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSI;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.LSEState = RCC_LSE_OFF;
RCC_OscInitStruct.LSIState = RCC_LSI_ON;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
LSE_Status = HAL_ERROR;
}
else
{
Error_Handler();
}
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC|RCC_PERIPHCLK_ADC;
if(HAL_ERROR == LSE_Status)
{
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI; // switch to LSI
}
else
{
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; // default to LSE
}
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
库函数 HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct) 也要做修改,修改如下:其中超时使用 RCC_Delay(1); // 72MHz,因为使用sysclick修改的代码量会很大。
/* Set the new LSE configuration -----------------------------------------*/
__HAL_RCC_LSE_CONFIG(RCC_OscInitStruct->LSEState);
/* Check the LSE State */
if (RCC_OscInitStruct->LSEState != RCC_LSE_OFF) // modify by anbin 9.14
{
rt_tick_t tick = 0;
/* Wait till LSE is ready */
while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET)
{
RCC_Delay(1); // 72MHz
tick++;
if (tick > RCC_LSE_TIMEOUT_VALUE/2) // time out, switch to LSI
{
return HAL_TIMEOUT;
}
}
}
修改的程序测试,上电等待5s左右程序开始正常运行,关闭了LSE,切换到了内部LSI,后期可以补充将RTC初始化失败的标记传到上位机。
作者:星元的天空