《嵌入式 – GD32开发实战指南》第16章 RTC

开发环境:
MDK:Keil 5.30
开发板:GD32F207I-EVAL
MCU:GD32F207IK

16.1 RTC工作原理
16.1.1 RTC简介
GD32 的 RTC 外设,实质是一个掉电后还继续运行的定时器。从定时器的角度来说,相对于通用定时器 TIMER 外设,它十分简单,只有很纯粹的计时功能(当然,可以触发中断);但从掉电还继续运行的角度来说,它却是 GD32中唯一一个具有如此强大功能的外设。所以 RTC 外设的复杂之处并不在于它的定时功能,而在于它掉电还继续运行的特性。

以上所说的掉电,是指主电源 VDD断开的情况,为了 RTC 外设掉电继续运行,必须给GD32芯片通过 VBAT引脚接上锂电池。当主电源 VDD有效时,由 VDD给 RTC 外设供电。当 VDD掉电后,由 VBAT给 RTC 外设供电。但无论由什么电源供电,RTC 中的数据都保存在属于 RTC 的备份域中,若主电源 VDD和 VBAT都掉电,那么备份域中保存的所有数据将丢失。备份域除了 RTC 模块的寄存器,还有 42 个 16 位的寄存器可以在 VDD掉电的情况下保存用户程序的数据,系统复位或电源复位时,这些数据也不会被复位。

从 RTC 的定时器特性来说,它是一个 32 位的计数器,只能向上计数。它使用的时钟源有三种,分别为高速外部时钟的 128 分频:HXTAL/128;低速内部时钟IRC40K;使 HXTAL分频时钟或IRC40K的话,在主电源 VDD掉电的情况下,这两个时钟来源都会受到影响,因此没法保证 RTC 正常工作。因此 RTC 一般使用低速外部时钟LXTAL,频率为实时时钟模块中常用的 32.768KHz,这是因为 32768 = 215,分频容易实现,所以它被广泛应用到 RTC 模块。在主电源 VDD有效的情况下(待机),RTC 还可以配置闹钟事件使 GD32退出待机模式。

RTC模块在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。RTC模块和时钟配置系统处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。

16.1.2主要特性
 可编程的预分频系数:分频系数最高为2^20
 32位的可编程计数器,可用于较长时间段的测量。
 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟频率的四分之一以上)。
 可以选择以下三种RTC的时钟源:

A) HXTAL 时钟除以 128
B) LXTAL 振荡电路时钟
C) IRC40K 振荡电路时钟

 2个独立的复位类型:

A) APB1接口由系统复位;
B) RTC核心(预分频器、闹钟、计数器和分频只能由后备域复位

 3个专门的可屏蔽中断:

A) 闹钟中断,用来产生一个软件可编程的闹钟中断。
B) 秒中断,用来产生一个可编程的周期性中断信号 (最长可达1秒)。
C) 溢出中断,指示内部可编程计数器溢出并回转为的状态。

16.1.3 RTC架构
RTC的架构如下图所示。

RTC 由两个主要部分组成, 第一部分(APB1 接口)用来和 APB1 总线相连。此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。 APB1 接口由 APB1 总线时钟驱动,用来与 APB1 总线连接。

另一部分(RTC 核心)由一组可编程计数器组成,RTC内核包含两个主要模块。一个是RTC预分频模块,用来产生RTC时间基准时钟SC_CLK。RTC预分频模块包含一个20位可编程分频器(RTC预分频器) ,该分频器可以通过对RTC时钟源分频产生SC_CLK。如果对RTC_INTEN寄存器中的秒中断标志位被使能, RTC会在每个SC_CLK上升沿产生一个秒中断。 另外一个模块是一个32 位可编程计数器,其数值可以被初始化为当前系统时间。如果对RTC_INTEN 寄存器的闹钟中断标志位被使能, RTC会在系统时间等于闹钟时间(存储于RTC_ALRMH/L 寄存器)时产生一个闹钟中断。

16.2 RTC寄存器分析
16.2.1 RTC寄存器描述
RTC 总共有 2 个控制寄存器RTC_INTEN和 RTC_CTL。

RTC_INTEN寄存器用来控制中断的,我们本章将要用到秒钟中断,所以在该寄存器必须设置最低位为 1,以允许秒钟中断。

RTC_CTL的第 0 位是秒钟标志位,我们在进入闹钟中断的时候,通过判断这位来决定是不是发生了秒钟中断。然后必须通过软件将该位清零(写0)。第 3 位为寄存器同步标志位,我们在修改控制寄存器之前,必须先判断该位,是否已经同步了,如果没有则等待同步,在没同步的情况下修改RTC_INTEN/RTC_CTL的值是不行的。第4位为配置标位,在软件修改 RTC_CNTx/RTC_ALRMx/RTC_PSCx的值的时候,必须先软件置位该位,以允许进入配置模式。第 5 位为 RTC 操作位,该位由硬件操作,软件只读。通过该位可以判断上次对 RTC 寄存器的操作是否完成,如果没有,我们必须等待上一次操作结束才能开始下一次操作。

【注意】
 任何标志位都将保持挂起状态,直到适当的RTC_CTL请求位被软件复位,表示所请求的中断已经被接受。
 在复位时禁止所有中断,无挂起的中断请求,可以对RTC寄存器进行写操作。
 当APB1时钟不运行时,SCIF、ALRMIF、OVIF和RSYNF位不被更新。
 SCIF、ALRMIF、OVIF和RSYNF位只能由硬件置位,由软件来清零。
 若ALRMIF =1且ALRMIE =1,则允许产生RTC全局中断。如果在EXTI控制器中允许产生EXTI线 17中断,则允许产生RTC全局中断和RTC闹钟中断。
 若ALRMIF =1,如果在EXTI控制器中设置了EXTI线 17的中断模式,则允许产生RTC闹钟中断;如果在EXTI控制器中设置了EXTI线 17的事件模式,则这条线上会产生一个脉冲(不会产生RTC闹钟中断)。

RTC 预分频装载寄存器,也有 2 个寄存器组成,RTC_PSCH和RTC_PSCL。这两个寄存器用来配置 RTC 时钟的分频数的,比如我们使用外部 32.768K 的晶振作为时钟的输入频率,那么我们要设置这两个寄存器的值为 32767,以得到一秒钟的计数频率。RTC_PSCH的各位描述如下图所示。

从上图可以看出,RTC_PSCH只有低四位有效,用来存储PSC的 19~16 位。而PSC的前 16 位,存放在RTC_PSCL里面,寄存器RTC_PSCL的各位描述如下图所示。

【注】如果输入时钟频率是32.768kHz(RTCCLK),这个寄存器中写入7FFFh可获得周期为1秒钟的信号。

RTC 预分频器寄存器也有 2 个寄存器组成 RTC_DIVH 和 RTC_DIVL,这两个寄存器的作用就是用来获得比秒钟更为准确的时钟,比如可以得到 0.1 秒,或者 0.01 秒等。该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新装载。这两个寄存器和 RTC 预分频装载寄存器的各位是一样的,这里我们就不列出来了。

接着要介绍的是 RTC 最重要的寄存器, RTC 计数器寄存器 RTC_CNT。该寄存器由 2 个 16位的寄存器组成 RTC_CNTH 和 RTC_CNTL,总共 32 位,用来记录秒钟值(一般情况下)。此两个计数器也比较简单,我们也不多说了。注意一点,在修改这个寄存器的时候要先进入配置模式。

最后我们介绍 RTC 部分的最后一个寄存器, RTC 闹钟寄存器,该寄存器也是由 2 个 16 为的寄存器组成 RTC_ALRH 和 RTC_ALRL。总共也是 32 位,用来标记闹钟产生的时间(以秒为单位),如果 RTC_CNT 的值与 RTC_ALR 的值相等,并使能了中断的话,会产生一个闹钟中断。该寄存器的修改也要进入配置模式才能进行。

因为我们使用到备份寄存器来存储 RTC 的相关信息(我们这里主要用来标记时钟是否已经经过了配置)。

16.2.2读RTC寄存器
RTC完全独立于RTC APB1接口。

软件通过APB1接口访问RTC的预分频值、计数器值和闹钟值。但是,相关的可读寄存器只在与RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。RTC标志也是如此的。

这意味着,如果APB1接口曾经被关闭,而读操作又是在刚刚重新开启APB1之后,则在第一次的内部寄存器更新之前,从APB1上读出的RTC寄存器数值可能被破坏了(通常读到0) 。下述几种情况下能够发生这种情形:

 发生系统复位或电源复位
 系统刚从待机模式唤醒
 系统刚从停机模式唤醒

所有以上情况中,APB1接口被禁止时(复位、无时钟或断电)RTC核仍保持运行状态。
因此,若在读取RTC寄存器时,RTC 的APB1 接口曾经处于禁止状态,则软件首先必须等待RTC_CTL寄存器中的RSYNF位(寄存器同步标志)被硬件置’1’。

16.2.3配置RTC寄存器
必须设置RTC_CTL寄存器中的CMF位,使 RTC进入配置模式后,才能写入 RTC_PSC、RTC_CNT、RTC_ALRM寄存器。

另外,对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CTL寄存器中的LWOFF状态位,判断RTC寄存器是否处于更新中。仅当LWOFF状态位是’1’时,才可以写入RTC寄存器。

配置过程:

1.查询LWOFF位,直到LWOFF的值变为’1’
2.置CMF值为1,进入配置模式
3.对一个或多个RTC寄存器进行写操作
4.清除CMF标志位,退出配置模式
5.查询LWOFF,直至LWOFF位变为’1’ 以确认写操作已经完成。
6.仅当CMF标志位被清除时,写操作才能进行,这个过程至少需要3个RTCCLK周期。

16.3 RTC具体代码实现
RTC 正常工作的一般配置步骤如下:

1)使能电源时钟和备份区域时钟。

前面已经介绍了,我们要访问 RTC 和备份区域就必须先使能电源时钟和备份区域时钟。

rcu_periph_clock_enable(RCU_BKPI);
rcu_periph_clock_enable(RCU_PMU);
1
2
2)取消备份区写保护。

要向备份区域写入数据,就要先取消备份区域写保护(写保护在每次硬复位之后被使能),否则是无法向备份区域写入数据的。我们需要用到向备份区域写入一个字节,来标记时钟已经配置过了,这样避免每次复位之后重新配置时钟。 取消备份区域写保护的库函数实现方法是:

pmu_backup_write_enable(); //使能 RTC 和后备寄存器访问
1
3)复位备份区域,开启外部低速振荡器。

在取消备份区域写保护之后,我们可以先对这个区域复位,以清除前面的设置,当然这个操作不要每次都执行,因为备份区域的复位将导致之前存在的数据丢失,所以要不要复位,要看情况而定。然后我们使能外部低速振荡器,注意这里一般要先判断 RCC_BDCR 的 LSERDY位来确定低速振荡器已经就绪了才开始下面的操作。

备份区域复位的函数是:

bkp_deinit();//复位备份区域
开启外部低速振荡器的函数是:
rcu_osci_on(RCU_LXTAL);// 开启外部低速振荡器
1
2
3
4)选择 RTC 时钟,并使能。
库函数中,选择 RTC 时钟的函数是:

rcu_rtc_clock_config(RCU_RTCSRC_LXTAL); //选择LXTAL作为 RTC 时钟
1
对于 RTC 时钟的选择使能 RTC 时钟的函数是:

rcu_periph_clock_enable(RCU_RTC); //使能 RTC 时钟
1
5)设置 RTC 的分频,以及配置 RTC 时钟。

在开启了 RTC 时钟之后,我们要做的就是设置 RTC 时钟的分频数,通过 RTC_PSCH 和RTC_PSCL 来设置,然后等待 RTC 寄存器操作完成,并同步之后,设置秒钟中断。然后设置RTC 的允许配置位( RTC_CTL的 CMF 位),设置时间(其实就是设置 RTC_CNTH 和 RTC_CNTL两个寄存器)。

下面我们一一这些步骤用到的库函数:

在进行 RTC 配置之前首先要打开允许配置位(CMF),库函数是:

rtc_configuration_mode_enter ();/// 允许配置
1
在配置完成之后,千万别忘记更新配置同时退出配置模式,函数是:

rtc_configuration_mode_exit ();//退出配置模式,更新配置
1
设置 RTC 时钟分频数, 库函数是:

void rtc_prescaler_set(uint32_t psc)
1
这个函数只有一个入口参数,就是 RTC 时钟的分频数,很好理解。
然后是设置秒中断允许,RTC 使能中断的函数是:

void rtc_interrupt_enable(uint32_t interrupt)
1
这个函数的第一个参数是设置秒中断类型,这些通过宏定义定义的。 对于使能秒中断方法是:

rtc_interrupt_enable(RTC_INT_SECOND); //使能 RTC 秒中断
1
下一步便是设置时间了,设置时间实际上就是设置 RTC 的计数值,时间与计数值之间是需要换算的,当然也可先不设置。库函数中设置 RTC 计数值的方法是:

void rtc_counter_set(uint32_t cnt)
1
通过这个函数直接设置 RTC 计数值。

6)更新配置,设置 RTC 中断分组。

在设置完时钟之后,我们将配置更新同时退出配置模式,这里还是通过 RTC_CTL的CMF来实现。库函数的方法是:

rtc_configuration_mode_exit ();//退出配置模式,更新配置
1
在退出配置模式更新配置之后我们在备份区域 BKP_DATA0中写入 0xA5A5代表我们已经初始化过时钟了,下次开机(或复位)的时候,先读取 BKP_DATA0的值,然后判断是否是 0xA5A5来决定是不是要配置。接着我们配置 RTC 的秒钟中断,并进行分组。

往备份区域写用户数据的函数是:

void bkp_data_write(bkp_data_register_enum register_number, uint16_t data)
1
这个函数的第一个参数就是寄存器的标号了,这个是通过宏定义定义的。 比如我们要往BKP_DATA0 写入 0xA5A5,方法是:

bkp_data_write(BKP_DATA_0, 0xA5A5);
1
同时,有写便有读,读取备份区域指定寄存器的用户数据的函数是:

uint16_t bkp_data_read(bkp_data_register_enum register_number)
1
这个函数就很好理解了,这里不做过多讲解。

设置中断分组的方法之前已经详细讲解过,这里不做重复讲解。

7)编写中断服务函数。
最后,我们要编写中断服务函数,在秒钟中断产生的时候,读取当前的时间值。

/**
  * @brief  This function handles RTC exception.
  * @param  None
  * @retval None
  */
void RTC_IRQHandler(void)
{
   if(rtc_flag_get(RTC_FLAG_SECOND)!=RESET)//读取中断标志
   {
           rtc_flag_clear(RTC_FLAG_SECOND);//清楚中断标志
            tim_bz=1;//秒中断标志
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
完成的配如下:

/**
  * @brief  RTC配置
  * @param  None
  * @retval None
  */
void rtc_configuration(void)
{
    /* enable PMU and BKPI clocks 使能电源时钟和备份区域时钟*/
    rcu_periph_clock_enable(RCU_BKPI);
    rcu_periph_clock_enable(RCU_PMU);

    /* allow access to BKP domain 允许访问BKP区域*/
    pmu_backup_write_enable();

    //复位备份区域,开启外部低速振荡器
    /* reset backup domain */
    bkp_deinit();

    /* enable LXTAL 使能外部低速晶振 32.768K */
    rcu_osci_on(RCU_LXTAL);
    /* wait till LXTAL is ready */
    rcu_osci_stab_wait(RCU_LXTAL);

    //选择 RTC 时钟,并使能
    /* select RCU_LXTAL as RTC clock source */
    rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);

    /* enable RTC Clock 使能RTC时钟     */
    rcu_periph_clock_enable(RCU_RTC);

    rtc_configuration_mode_enter();

    /* wait for RTC registers synchronization */
    rtc_register_sync_wait();

    /* wait until last write operation on RTC registers has finished 等待写RTC寄存器完成*/
    rtc_lwoff_wait();

    /* enable the RTC second interrupt 使能RTC秒中断*/
    rtc_interrupt_enable(RTC_INT_SECOND);

    /* wait until last write operation on RTC registers has finished 等待写RTC寄存器完成*/
    rtc_lwoff_wait();

    /* set RTC prescaler: set RTC period to 1s 设置预分频*/
    rtc_prescaler_set(32767);

    /* wait until last write operation on RTC registers has finished 等待写RTC寄存器完成*/
    rtc_lwoff_wait();

    nvic_priority_group_set(NVIC_PRIGROUP_PRE1_SUB3);
    nvic_irq_enable(RTC_IRQn, 1, 0);
}

/**
  * @brief  RTC时钟初始化
  * @param  None
  * @retval None
  */
void clock_init(void)
{
    if(0xA5A5 != bkp_data_read(BKP_DATA_0))
    {
        //第一次运行  初始化设置
        rtc_configuration();//RTC初始化
        /* wait until last write operation on RTC registers has finished */
        rtc_lwoff_wait();
        /* change the current time */
        rtc_counter_set(0);
        /* wait until last write operation on RTC registers has finished */
        rtc_lwoff_wait();
        rtc_lwoff_wait();//等待写RTC寄存器完成      
        rtc_lwoff_wait();//等待写RTC寄存器完成
        bkp_data_write(BKP_DATA_0, 0xA5A5);//写配置标志
    }
    else
    {
        /* check if the power on reset flag is set */
        if(rcu_flag_get(RCU_FLAG_PORRST) != RESET)
        {
            printf("\r\n\n Power On Reset occurred....");
        }
        else if(rcu_flag_get(RCU_FLAG_SWRST) != RESET)
        {
            /* check if the pin reset flag is set */
            printf("\r\n\n External Reset occurred....");
        }
        printf("\r\n No need to configure RTC....");
        rtc_register_sync_wait();//等待RTC寄存器同步
        rtc_interrupt_enable(RTC_INT_SECOND);//使能RTC秒中断
        rtc_lwoff_wait();//等待写RTC寄存器完成
    }
    rtc_configuration_mode_exit();//退出配置模式, 更新配置
    rcu_all_reset_flag_clear();//清除复位标志;
}
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
主函数代码如下:

/*
    brief      main function
    param[in]  none
    param[out] none
    retval     none
*/
int main(void)
{
    uint32_t TimeData=0,hh=0,mm=0,ss=0;

    //usart init 115200 8-N-1
    com_init(COM1);

    rtc_configuration();
    clock_init();

    while(1)
    {
        if(tim_bz==1)
        {
            tim_bz=0;
            TimeData=rtc_counter_get();
            hh=    TimeData/3600;
            mm = (TimeData%3600)/60;
            ss = TimeData%60;
            printf("Time: %0.2d:%0.2d:%0.2d\r\n",hh,mm,ss);  
        }
    }
}
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
16.4实验现象
打开串口助手,打印信息如下:
————————————————
版权声明:本文为CSDN博主「Bruceoxl」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/bruceoxl/article/details/126358693

https://www.codetd.com/article/1445410

RTC是什么鬼?

RTC ----  REAL Time Clock----->实时时钟(年、月、日、星期、时、分、秒)

RTC的使用:

1)设置当前的时间

2)RTC可以自动的工作(嵌入式平台中,RTC也会不准)--->获取网络时间校正          --->GPS和北斗校时                               

3)可以设置闹钟时间,产生闹钟中断。

ARM中的RTC模块

ARM的电源原理图  

既有电源供电,也有电池供电,但没电时就像电子表一样纽扣电池供电

S5PV210中RTC的特点

1、Supports BCD Number, that is Second, Minute, Hour, Day of the week, Day, Month, and Year. 

BCD码(压缩型的BCD码)---> 32秒 --->second寄存器的内容 0x32

压缩型的BCD码是8位的,高4位是十位,第4位是各位。

2、Supports Leap Year Generator 支持闰年

3、Supports Alarm Function, that is ,Alarm-Interrupt or Wake-up from power down modes (idle, deep idle, stop,deep stop, and sleep) 支持闹钟功能,闹钟中断

4、Supports Tick Counter Function, that is Tick-Interrupt or Wake-up from power down modes (idle, deep idle,stop, deep stop, and sleep) 支持滴答功能,也就是每秒产生一次中断

5、Supports Independent Power Pin (RTCVDD) 支持独立电源,就是纽扣电池供电

6、Supports millisecond tick time interrupt for RTOS kernel time tick . 

RTC的TICK时钟,可以提供给实时操作系统的内核,作为实时操作系统内核时钟。

RTC的寄存器

配置寄存器    

闹钟的设置寄存器   

RTC时间设置和读取的寄存器

1、中断判断寄存器---INTP

1、RTC的控制寄存器----RTCCON

1、滴答时钟计数器寄存器----TICCNT

例:设置tick timer的周期是500ms,则频率2Hz。

1)设置tick timer的clock频率:

RTCCON &= ~(0xf<<4);  //tick timer的基准频率32768Hz

2)设置tick timer的计数值

TICNT = 32768/2;  //tick timer的工作频率2Hz

基准频率/计数值 = 工作频率

基准频率/工作频率=计数值

4、RTC的闹钟寄存器---RTCALM

设置闹钟的比较时间。

四、RTC的程序

#include "main.h"
#include "stdio.h"
#include "int.h"
extern unsigned int m;
#define 	RTC_BASE	 (0xE2800000)
#define		rINTP      	 (*((volatile unsigned long *)(RTC_BASE + 0x30)))
#define		rRTCCON    	 (*((volatile unsigned long *)(RTC_BASE + 0x40)))
#define		rTICCNT    	 (*((volatile unsigned long *)(RTC_BASE + 0x44)))
#define		rRTCALM    	 (*((volatile unsigned long *)(RTC_BASE + 0x50)))
#define		rALMSEC    	 (*((volatile unsigned long *)(RTC_BASE + 0x54)))
#define		rALMMIN    	 (*((volatile unsigned long *)(RTC_BASE + 0x58)))
#define		rALMHOUR  	 (*((volatile unsigned long *)(RTC_BASE + 0x5c)))
#define		rALMDATE     (*((volatile unsigned long *)(RTC_BASE + 0x60)))
#define		rALMMON    	 (*((volatile unsigned long *)(RTC_BASE + 0x64)))
#define		rALMYEAR  	 (*((volatile unsigned long *)(RTC_BASE + 0x68)))
#define		rRTCRST      (*((volatile unsigned long *)(RTC_BASE + 0x6c)))
#define		rBCDSEC    	 (*((volatile unsigned long *)(RTC_BASE + 0x70)))
#define		rBCDMIN   	 (*((volatile unsigned long *)(RTC_BASE + 0x74)))
#define		rBCDHOUR     (*((volatile unsigned long *)(RTC_BASE + 0x78)))
#define		rBCDDATE     (*((volatile unsigned long *)(RTC_BASE + 0x7c)))
#define		rBCDDAY      (*((volatile unsigned long *)(RTC_BASE + 0x80)))
#define		rBCDMON      (*((volatile unsigned long *)(RTC_BASE + 0x84)))
#define		rBCDYEAR     (*((volatile unsigned long *)(RTC_BASE + 0x88)))
#define		rCURTICCNT   (*((volatile unsigned long *)(RTC_BASE + 0x90)))
#define		rRTCLVD    	 (*((volatile unsigned long *)(RTC_BASE + 0x94)))


unsigned int alarm_flag = 1; //1---alarm没有工作; 
          
// 函数功能:把十进制num转成bcd码,譬如把56转成0x56,num-->dec
static unsigned int num_2_bcd(unsigned int num)
{
	// 第一步,把56拆分成5和6 
	// 第二步,把5和6组合成0x56
	return (((num / 10)<<4) | (num % 10));
}
#if 1
// 函数功能:把bcd码bcd转成十进制,譬如把0x56转成56
static unsigned int bcd_2_num(unsigned int bcd)
{
	// 第一步,把0x56拆分成5和6 
	// 第二步,把5和6组合成56
	return (((bcd & 0xf0)>>4)*10 + (bcd & (0x0f)));
}
#endif
void rtc_alarm_init(void)
{
	rALMMIN = num_2_bcd(1);  //闹钟的分是1
	rRTCALM |= 1<<1;//分钟匹配成功,产生闹钟中断
	rRTCALM |= 1<<6; //打开闹钟中断功能
}

void isr_rtc_alarm(void) //闹钟的ISR<--当RTC的分和ALARM分一致
{
	static int i = -1; 
	printf("rtc alarm, i = %d...\r\n", i++); //debug
	GPD0DAT |= (1<<0); //beep 响
	alarm_flag = 0;
	rINTP |= (1<<1); //清中断
	intc_clearvectaddr();
}

void rtc_tick_init(void)
{
	
	rRTCCON &= ~(0xf<<4); //clock = 32768
	rTICCNT = 26214;//32768/(1.25);
	printf("---rtc write time test  2016-9-13 2 15:59:03 ---\n\r");
	struct rtc_time tWrite = 
	{
		.year = 2016,
		.month = 9,
		.date = 13,
		.hour = 15,
		.minute = 59,
		.second = 3,
		.day = 2,
	};
	rtc_set_time(&tWrite);
	rRTCCON |= (1<<8); //enable tick timer
}

void rtc_set_time(const struct rtc_time *p)
{
	// 第一步,打开RTC读写开关
	rRTCCON |= (1<<0);
	
	// 第二步,写RTC时间寄存器
	rBCDYEAR = num_2_bcd(p->year - 2000);
	rBCDMON = num_2_bcd(p->month);
	rBCDDATE = num_2_bcd(p->date);
	rBCDHOUR = num_2_bcd(p->hour);
	rBCDMIN = num_2_bcd(p->minute);
	rBCDSEC = num_2_bcd(p->second);
	rBCDDAY = num_2_bcd(p->day);
	
	// 最后一步,关上RTC的读写开关
	rRTCCON &= ~(1<<0);
}

void rtc_get_time(struct rtc_time *p)
{
	// 第一步,打开RTC读写开关
	rRTCCON |= (1<<0);
	
	// 第二步,读RTC时间寄存器
	p->year = bcd_2_num(rBCDYEAR) + 2000;
	p->month = bcd_2_num(rBCDMON);
	p->date = bcd_2_num(rBCDDATE);
	p->hour = bcd_2_num(rBCDHOUR);
	p->minute = bcd_2_num(rBCDMIN);
	p->second = bcd_2_num(rBCDSEC);
	p->day = bcd_2_num(rBCDDAY);
	
	// 最后一步,关上RTC的读写开关
	rRTCCON &= ~(1<<0);
}

void isr_rtc_tick(void) //Tick timer 的ISR (800ms)
{
	if(alarm_flag == 0)
		m++;  //每800ms 加一次1
	if(m == 12) //10s
	{
		GPD0DAT &= ~(1<<0);  //beep不响
		m = 0;
		alarm_flag = 1;
	}
	struct rtc_time tRead;
	rtc_get_time(&tRead);
	printf("The time read is: %d:%d:%d:%d:%d:%d:%d.\r\n", 
			tRead.year, tRead.month, tRead.date, tRead.hour, 
			tRead.minute, tRead.second, tRead.day);
	rINTP |= (1<<0); //清中断
	intc_clearvectaddr();
}

版权声明:本文为u012294613原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。