STM32标准外设库RTC SPL篇25

Posted:   2020-09-17

Status:   Completed

Tags :   STM32 SPL

Categories :   STM32 SPL

Previous:   STM32标准外设库触摸屏 SPL篇24

Next:   新建STemWin工程STemWin篇1


RTC常用函数

  • 等待时钟同步和操作完成void RTC_WaitForSynchro(void)
  • 等待上一次对 RTC 寄存器的操作完成void RTC_WaitForLastTask(void)
  • 使能备份域访问void PWR_BackupAccessCmd(FunctionalState NewState)
  • 进入RTC 配置模式void RTC_EnterConfigMode(void)
  • 退出RTC 配置模式void RTC_ExitConfigMode(void)
  • 设置RTC 时钟分频void RTC_SetPrescaler(uint32_t PrescalerValue)
  • 获取RTC 计数器uint32_t RTC_GetCounter(void)
  • 设置RTC 计数器void RTC_SetCounter(uint32_t CounterValue)
  • 设置 RTC 闹钟的值void RTC_SetAlarm(uint32_t AlarmValue)

新建工程

添加库函数

  • 打开 Manage Run-Time Environment
  • 选择Device->Startup
  • 选择Device->StdPeriph Drivers->GPIO
  • 选择Device->StdPeriph Drivers->USART
  • 选择Device->StdPeriph Drivers->RTC
  • 选择Device->StdPeriph Drivers->BKP
  • 选择Device->StdPeriph Drivers->PWR
  • 点击Resolve会自动选择其他必须的选择

RTC程序

  • my_RTC.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#ifndef __RTC_H
#define __RTC_H
#include "stm32f10x.h"

//使用LSE外部时钟 或 LSI内部时钟
#define RTC_CLOCK_SOURCE_LSE  
#define RTC_BKP_DRX          BKP_DR1
// 写入到备份寄存器的数据宏定义
#define RTC_BKP_DATA         0xA5A5
//北京时间的时区秒数差
#define TIME_ZOOM						(8*60*60)

typedef struct  {
	int tm_sec;
	int tm_min;
	int tm_hour;
	int tm_mday;
	int tm_mon;
	int tm_year;
}rtc_time;

void RTC_NVIC_Config(void);
void Time_Regulate_Get(rtc_time *tm);
void Time_Adjust(rtc_time *tm);
void Time_Display(uint32_t TimeVar,rtc_time *tm);
void RTC_CheckAndConfig(rtc_time *tm);
#endif

  • my_RTC.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include "my_RTC.h"
#include <stdio.h>
#define FEBRUARY		2
#define	STARTOFTIME		1970
#define SECDAY			86400L           /*  一天有多少s */
static uint8_t month_days[12] = {	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
#define leapyear(year)		((year % 4 == 0)&&(((year %100)!=0)||((year % 400)==0)))
#define	days_in_year(a) 	(leapyear(a) ? 366 : 365)
#define	days_in_month(a) 	(month_days[(a) - 1])

/* 秒中断标志,进入秒中断时置1,当时间被刷新之后清0 */
__IO uint32_t TimeDisplay = 0;


/**
  * @brief  移植于Linux的时间计算函数
  * @param  时间
  * @retval 时间戳
  */
static uint32_t mktime(rtc_time *tm)
{
    if (0 >= (int) (tm->tm_mon -= 2)) {	/* 1..12 -> 11,12,1..10 */
        tm->tm_mon += 12;		/* Puts Feb last since it has leap day */
        tm->tm_year -= 1;
    }

    return (((
                 (uint32_t) (tm->tm_year/4 - tm->tm_year/100 + tm->tm_year/400 + 367*tm->tm_mon/12 + tm->tm_mday) +
                 tm->tm_year*365 - 719499
             )*24 + tm->tm_hour /* now have hours */
            )*60 + tm->tm_min /* now have minutes */
           )*60 + tm->tm_sec; /* finally seconds */
}
/**
  * @brief  移植于Linux的时间计算函数
  * @param  tim 时间戳
  * @param  tm 时间
  * @retval 无
  */
void to_tm(uint32_t tim, rtc_time * tm)
{
    register uint32_t    i;
    register long   hms, day;

    day = tim / SECDAY;			/* 有多少天 */
    hms = tim % SECDAY;			/* 今天的时间,单位s */

    /* Hours, minutes, seconds are easy */
    tm->tm_hour = hms / 3600;
    tm->tm_min = (hms % 3600) / 60;
    tm->tm_sec = (hms % 3600) % 60;

    /* Number of years in days */ /*算出当前年份,起始的计数年份为1970年*/
    for (i = STARTOFTIME; day >= days_in_year(i); i++) {
        day -= days_in_year(i);
    }
    tm->tm_year = i;

    /* Number of months in days left */ /*计算当前的月份*/
    if (leapyear(tm->tm_year)) {
        days_in_month(FEBRUARY) = 29;
    }
    for (i = 1; day >= days_in_month(i); i++) {
        day -= days_in_month(i);
    }
    days_in_month(FEBRUARY) = 28;
    tm->tm_mon = i;

    /* Days are what is left over (+1) from all that. *//*计算当前日期*/
    tm->tm_mday = day + 1;
}
/**
  * @brief  配置RTC
  * @param  无
  * @retval 无
  */
static void RTC_Configuration(void)
{
    /* 使能 PWR 和 Backup 时钟 */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

    /* 允许访问 Backup 区域 */
    PWR_BackupAccessCmd(ENABLE);

    /* 复位 Backup 区域 */
    BKP_DeInit();

//使用外部时钟还是内部时钟(在bsp_rtc.h文件定义)
//使用外部时钟时,在有些情况下晶振不起振
//批量产品的时候,很容易出现外部晶振不起振的情况,不太可靠
#ifdef 	RTC_CLOCK_SOURCE_LSE
    /* 使能 LSE */
    RCC_LSEConfig(RCC_LSE_ON);

    /* 等待 LSE 准备好 */
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
    {}

    /* 选择 LSE 作为 RTC 时钟源 */
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);

    /* 使能 RTC 时钟 */
    RCC_RTCCLKCmd(ENABLE);

    /* 等待 RTC 寄存器 同步
     * 因为RTC时钟是低速的,内环时钟是高速的,所以要同步
     */
    RTC_WaitForSynchro();

    /* 确保上一次 RTC 的操作完成 */
    RTC_WaitForLastTask();

    /* 使能 RTC 秒中断 */
    RTC_ITConfig(RTC_IT_SEC, ENABLE);

    /* 确保上一次 RTC 的操作完成 */
    RTC_WaitForLastTask();

    /* 设置 RTC 分频: 使 RTC 周期为1s  */
    /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) = 1HZ */
    RTC_SetPrescaler(32767);

    /* 确保上一次 RTC 的操作完成 */
    RTC_WaitForLastTask();

#else

    /* 使能 LSI */
    RCC_LSICmd(ENABLE);

    /* 等待 LSI 准备好 */
    while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET)
    {}

    /* 选择 LSI 作为 RTC 时钟源 */
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);

    /* 使能 RTC 时钟 */
    RCC_RTCCLKCmd(ENABLE);

    /* 等待 RTC 寄存器 同步
     * 因为RTC时钟是低速的,内环时钟是高速的,所以要同步
     */
    RTC_WaitForSynchro();

    /* 确保上一次 RTC 的操作完成 */
    RTC_WaitForLastTask();

    /* 使能 RTC 秒中断 */
    RTC_ITConfig(RTC_IT_SEC, ENABLE);

    /* 确保上一次 RTC 的操作完成 */
    RTC_WaitForLastTask();

    /* 设置 RTC 分频: 使 RTC 周期为1s ,LSI约为40KHz */
    /* RTC period = RTCCLK/RTC_PR = (40 KHz)/(40000-1+1) = 1HZ */
    RTC_SetPrescaler(40000-1);

    /* 确保上一次 RTC 的操作完成 */
    RTC_WaitForLastTask();
#endif

}
/**
  * @brief  时间调节
  * @param  tm 时间
  * @retval 无
  */
void Time_Adjust(rtc_time *tm)
{

    /* RTC 配置 */
    RTC_Configuration();

    /* 等待确保上一次操作完成 */
    RTC_WaitForLastTask();

    /* 由日期计算时间戳并写入到RTC计数寄存器 */
    RTC_SetCounter(mktime(tm)-TIME_ZOOM);

    /* 等待确保上一次操作完成 */
    RTC_WaitForLastTask();
}
/**
  * @brief  配置RTC秒中断
  * @param  无
  * @retval 无
  */
void RTC_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Enable the RTC Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

/**
  * @brief  检查并配置RTC
  * @param  tm 时间
  * @retval 无
  */
void RTC_CheckAndConfig(rtc_time *tm)
{
    /* 使能 PWR 和 Backup 时钟 */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

    /* 允许访问 Backup 区域 */
    PWR_BackupAccessCmd(ENABLE);
    /*在启动时检查备份寄存器BKP_DR1,如果内容不是0xA5A5,
      则需重新配置时间并询问用户调整时间*/
    if (BKP_ReadBackupRegister(RTC_BKP_DRX) != RTC_BKP_DATA)
    {
        printf("\r\n\r\n RTC not yet configured....");
        printf("\r\n\r\n RTC configured....");

        /* 使用tm的时间配置RTC寄存器 */
        Time_Adjust(tm);

        /*向BKP_DR1寄存器写入标志,说明RTC已在运行*/
        BKP_WriteBackupRegister(RTC_BKP_DRX, RTC_BKP_DATA);
    }
    else
    {
        uint32_t RTC_CLOCK_SOURCE;
        RTC_CLOCK_SOURCE=RCC->BDCR&RCC_RTCCLKSource_HSE_Div128;
#ifdef RTC_CLOCK_SOURCE_LSE

        if(RTC_CLOCK_SOURCE!=RCC_RTCCLKSource_LSE)
        {
            printf("\r\n\r\n RTC CLOCK Changed....");
            printf("\r\n\r\n RTC configured....");
            uint32_t BJ_TimeVar;
            if(RTC_CLOCK_SOURCE==RCC_RTCCLKSource_LSI)
            {
                /* 使能 LSI */
                RCC_LSICmd(ENABLE);

                /* 等待 LSI 准备好 */
                while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET)
                {}
            }
            /*等待寄存器同步*/
            RTC_WaitForSynchro();
            /*  把标准时间转换为北京时间*/
            BJ_TimeVar =RTC_GetCounter() + TIME_ZOOM;

            to_tm(BJ_TimeVar, tm);/*把定时器的值转换为北京时间*/
            Time_Adjust(tm);
            /*向BKP_DR1寄存器写入标志,说明RTC已在运行*/
            BKP_WriteBackupRegister(RTC_BKP_DRX, RTC_BKP_DATA);
        }
#elif defined RTC_CLOCK_SOURCE_LSI
        if(RTC_CLOCK_SOURCE!=RCC_RTCCLKSource_LSI)
        {
            printf("\r\n\r\n RTC CLOCK Changed....");
            printf("\r\n\r\n RTC configured....");
            uint32_t BJ_TimeVar;
            /*等待寄存器同步*/
            RTC_WaitForSynchro();
            /*  把标准时间转换为北京时间*/
            BJ_TimeVar =RTC_GetCounter() + TIME_ZOOM;

            to_tm(BJ_TimeVar, tm);/*把定时器的值转换为北京时间*/
            Time_Adjust(tm);
            /*向BKP_DR1寄存器写入标志,说明RTC已在运行*/
            BKP_WriteBackupRegister(RTC_BKP_DRX, RTC_BKP_DATA);
        }
#endif

        /*LSE启动无需设置新时钟*/

#ifdef RTC_CLOCK_SOURCE_LSI
        /* 使能 LSI */
        RCC_LSICmd(ENABLE);

        /* 等待 LSI 准备好 */
        while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET)
        {}
#endif

        /*检查是否掉电重启*/
        if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
        {
            printf("\r\n\r\n Power On Reset occurred....");
        }
        /*检查是否Reset复位*/
        else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
        {
            printf("\r\n\r\n External Reset occurred....");
        }

        printf("\r\n No need to configure RTC....");

        /*等待寄存器同步*/
        RTC_WaitForSynchro();

        /*允许RTC秒中断*/
        RTC_ITConfig(RTC_IT_SEC, ENABLE);

        /*等待上次RTC寄存器写操作完成*/
        RTC_WaitForLastTask();
    }
    /* 清除复位标志 flags */
    RCC_ClearFlag();

}
/**
  * @brief  保存用户使用串口设置的时间
  * @param  tm 时间
  * @retval 无
  */
void Time_Regulate_Get(rtc_time *tm)
{
    uint32_t temp_num = 0;
    uint8_t day_max=0 ;

    printf("\r\n=========================设置时间==================");

    do
    {
        printf("\r\n  请输入年份(Please Set Years),范围[1970~2038],输入字符后请加回车:");
        scanf("%d",&temp_num);
        if(temp_num <1970 || temp_num >2038)
        {
            printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);

        }
        else
        {
            printf("\n\r  年份被设置为: %d\n\r", temp_num);

            tm->tm_year = temp_num;
            break;
        }
    } while(1);


    do
    {
        printf("\r\n  请输入月份(Please Set Months):范围[1~12],输入字符后请加回车:");
        scanf("%d",&temp_num);
        if(temp_num <1 || temp_num >12)
        {
            printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);

        }
        else
        {
            printf("\n\r  月份被设置为: %d\n\r", temp_num);

            tm->tm_mon = temp_num;
            break;
        }
    } while(1);

    /*根据月份计算最大日期*/
    switch(tm->tm_mon)
    {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:
        day_max = 31;
        break;

    case 4:
    case 6:
    case 9:
    case 11:
        day_max = 30;
        break;

    case 2:
        /*计算闰年*/
        if(leapyear(tm->tm_year))
        {
            day_max = 29;
        } else
        {
            day_max = 28;
        }
        break;
    }

    do
    {
        printf("\r\n  请输入日期(Please Set Months),范围[1~%d],输入字符后请加回车:",day_max);
        scanf("%d",&temp_num);

        if(temp_num <1 || temp_num >day_max)
        {
            printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);
        }
        else
        {
            printf("\n\r  日期被设置为: %d\n\r", temp_num);

            tm->tm_mday = temp_num;
            break;
        }
    } while(1);

    do
    {
        printf("\r\n  请输入时钟(Please Set Hours),范围[0~23],输入字符后请加回车:");
        scanf("%d",&temp_num);

        if( temp_num >23)
        {
            printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);
        }
        else
        {
            printf("\n\r  时钟被设置为: %d\n\r", temp_num);

            tm->tm_hour = temp_num;
            break;
        }
    } while(1);

    do
    {
        printf("\r\n  请输入分钟(Please Set Minutes),范围[0~59],输入字符后请加回车:");
        scanf("%d",&temp_num);

        if( temp_num >59)
        {
            printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);
        }
        else
        {
            printf("\n\r  分钟被设置为: %d\n\r", temp_num);

            tm->tm_min = temp_num;
            break;
        }
    } while(1);

    do
    {
        printf("\r\n  请输入秒钟(Please Set Seconds),范围[0~59],输入字符后请加回车:");
        scanf("%d",&temp_num);

        if( temp_num >59)
        {
            printf("\r\n 您输入的数字是:%d,不符合要求",temp_num);
        }
        else
        {
            printf("\n\r  秒钟被设置为: %d\n\r", temp_num);

            tm->tm_sec = temp_num;
            break;
        }
    } while(1);

}


/**
  * @brief  显示当前时间值
  * @param  TimeVar RTC计数值
  * @param  tm 时间
  * @retval 无
  */
void Time_Display(uint32_t TimeVar,rtc_time *tm)
{
    static uint32_t FirstDisplay = 1;
    uint32_t BJ_TimeVar;

    /*  把标准时间转换为北京时间*/
    BJ_TimeVar =TimeVar + TIME_ZOOM;

    to_tm(BJ_TimeVar, tm);/*把定时器的值转换为北京时间*/

    if((!tm->tm_hour && !tm->tm_min && !tm->tm_sec)  || (FirstDisplay))
    {
        printf("\r\n 今天:%0.4d,%0.2d,%0.2d", tm->tm_year, tm->tm_mon, tm->tm_mday);

        FirstDisplay = 0;
    }

    /* 输出时间戳,公历时间 */
    printf(" UNIX时间戳 = %d 当前时间为: %0.4d年 %0.2d月 %0.2d日   %0.2d:%0.2d:%0.2d\r\n",TimeVar,
           tm->tm_year, tm->tm_mon, tm->tm_mday,
           tm->tm_hour,tm->tm_min, tm->tm_sec);
}

中断函数

  • stm32f1xx_it.c添加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
extern uint32_t TimeDisplay;
/**
  * @brief  This function handles RTC interrupt request.
  * @param  None
  * @retval None
  */
void RTC_IRQHandler(void)
{
	  if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
	  {
	    /* Clear the RTC Second interrupt */
	    RTC_ClearITPendingBit(RTC_IT_SEC);
	
	    /* Enable time update */
	    TimeDisplay = 1;
	
	    /* Wait until last write operation on RTC registers has finished */
	    RTC_WaitForLastTask();
	  }
}

主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "my_gpio.h"
#include "my_usart.h"
#include "my_RTC.h"

/*时间结构体,默认时间2000-01-01 00:00:00*/
rtc_time systmtime=
{
    0,0,0,1,1,2000
};

extern __IO uint32_t TimeDisplay ;

/**
 * @brief  板级外设初始化,所有板子上的初始化均可放在这个函数里面
 * @param  无
 * @retval 无
 */
static void BSP_Init(void)
{
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
    /* LED 初始化 */
    LED_GPIO_Config();
    /* 串口初始化	*/
    USART_Config();
}
int main(void)
{
    BSP_Init();
    /* 配置RTC秒中断优先级 */
    RTC_NVIC_Config();
    RTC_CheckAndConfig(&systmtime);
    while(1)
    {
        /* 每过1s 更新一次时间*/
        if (TimeDisplay == 1)
        {
            /* 当前时间 */
            Time_Display( RTC_GetCounter(),&systmtime);
            TimeDisplay = 0;
        }

        //按下按键,通过串口修改时间
        if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON  )
        {
            rtc_time set_time;

            /*使用串口接收设置的时间,输入数字时注意末尾要加回车*/
            Time_Regulate_Get(&set_time);
            /*用接收到的时间设置RTC*/
            Time_Adjust(&set_time);

            //向备份寄存器写入标志
            BKP_WriteBackupRegister(RTC_BKP_DRX, RTC_BKP_DATA);

        }
    }
}

调试

  • 编译之后下载到开发板
  • 打开串口助手
  • 可以看到串口每1秒接收一次时间
  • 按下按键可以在串口设置时间