嵌入式裸机定时器、看门狗和RTC学习笔记
一. 定时器(timer)
1.1 什么是定时器(timer)
定时器是SoC中常见外设
(1)定时器与计数器。计数器是用来计数的(每隔一个固定时间会计一个数);因为计数器的计数时间周期是固定的,因此到了一定时间只要用计数值×计数时间周期,就能得到一个时间段,这个时间段就是我们定的时间(这就是定时器了)。
(2)定时器/计数器作为SoC的外设,主要用来实现定时执行代码的功能。定时器相对于SoC来说,就好象闹钟相对于人来说意义一样。
1.2 定时器有什么用
定时器可以让SoC在执行主程序的同时,可以(通过定时器)具有计时功能,到了一定时间(计时结束)后,定时器会产生中断提醒CPU,CPU会去处理中断并执行定时器中断的ISR。从而去执行预先设定好的事件 ,定时器就好象是CPU的一个秘书一样,这个秘书专门管帮CPU来计时。
1.3 定时器的原理
(1)定时器计时其实是通过计数来实现的。定时器内部有一个计数器,这个计数器根据一个时钟(这个时钟源来自于ARM的APB总线,然后经过时钟模块内部的分频器来分频得到)来工作。每隔一个时钟周期,计数器就计数一次,定时器的时间就是计数器计数值×时钟周期。
(2)定时器内部有1个寄存器TCNT,计时开始时我们会把一个总的计数值(譬如说300)放入TCNT寄存器中,然后每隔一个时钟周期(假设为1ms)TCNT中的值会自动减1(硬件自动完成,不需要CPU软件去干预),直到TCNT中减为0的时候,TCNT就会触发定时器中断。
(3)定时时间是由2个东西共同决定的:一个是TCNT中的计数值,一个是时钟周期。譬如上例中,定时周期就为300×1ms = 300ms。
1.4 S5PV210中的四类定时器
1.4.1 PWM定时器
这种是最常用的,平时所说的定时器一般指的是这个。像简单单片机(譬如51单片机)中的定时器也是这类 。
脉冲宽度调制(Pulse Width Modulation)通常由一列占空比不同的矩形脉冲构成,其占空比与信号的瞬时采样值成比例。
1.4.2 系统定时器
(1)系统(指的是操作系统)定时器,系统定时器也是用来产生固定时间间隔(TCNT×时钟周期)信号的,称为systick,这个systick用来给操作系统提供tick信号。
(2)产生systick作为操作系统的时间片(time slice)的。
(3)一般做操作系统移植的时候,一般原厂提供的基础移植部分就已经包含.
1.4.3 看门狗定时器
(1)看门狗定时器本质上也是一个定时器,和上面2个没有任何本质区别,看门狗定时器可以设置在时间到了的时候产生中断,也可以选择发出复位信号复位CPU。
(2)看门狗定时器在实践中应用很多,尤其是工业领域(环境复杂、干扰多)机器容易出问题,而且出问题后后果很严重,此时一般都会用看门狗来进行系统复位。
1.4.4 实时时钟RTC(real time clock)
(1)区分时间段和时间点。时间段是相对的,两个时间点相减就会得到一个时间段;而时间点是绝对的,是绝无仅有的一个时间点。
(2)定时器关注的是时间段(而不是时间点),定时器计时从开启定时器的那一刻开始,到定的时间段结束为止产生中断;RTC中工作用的是时间点(xx年x月x日x时x分x秒星期x)。
(3)RTC和定时器的区别,就相当于是钟表和闹钟的区别。
1.5 S5PV210中的PWM定时器
1.5.1 PWM定时器实现过程与框图
S5PV210 有五个PWM定时器,定时器使用APB-PCLK作为时钟源,框图如下所示。
(1)先经过一个8bit的预分频器(TCFG0),然后通过一个分频器(TCFG1)
(2)下来会在TCNTBx 中加载总的计数值,这个数值会按照前一步的1/频率 逐步递减
(3)并且不断与TCMPBx中的数值进行对比,如果TCNTBx<TCMPBx ,输出电平就会发生转换
(4)计时器计数器(TCNTBx)达到零,则对应的TCNTBn的值将自动重新加载到下一个计数器中,以启动下一个循环。
1.5.2 PWM 定时器实现相关寄存器
TCFG0 : 8bit 的预分频器
TCFG1 :5个通道的MUX(实质为分频器)
TCON :定时器控制寄存器
TCNTBx :用于计数的计数器
TCMPBx :对比寄存器,用来实现占空比的的设定
TCNTOx : 监视观察寄存器
TINT_CSTAT :计数器中断控制和状态寄存器
1.5.3 注意点
PCLK:由之前时钟系统章节所知此处的时钟源为PCLK_PSYS = 66 MHz.
自动重载和双缓冲(auto-reload and double buffering)
(1)定时器工作的时候,一次定时算一个工作循环。定时器默认是单个循环工作的,也就是说定时一次,计时一次,到期中断一次就完了。下次如果还要再定时中断,需要另外设置。
(2)但是现实中用定时器来做的时候往往是循环的,最简单最笨的方法就是写代码反复重置定时器寄存器的值(在每次中断处理的isr中再次给TCNTB中赋值,再次刷到TCNT中再次启动定时器),早期的单片机定时器就是这样的;但是现在的高级SoC中的定时器已经默认内置了这种循环定时工作模式,就叫自动装载(auto-reload)机制。
(3)自动装载机制就是当定时器初始化好开始计时后再不用管了,他一个周期到了后会自己从TCNTB中再次装载值到TCNT中,再次启动定时器开始下个循环。
PWM波形的生成原理
(1)PWM波形其实就是用时间来控制电平高低,所以用定时器来实现PWM波形是天经地义的。
(2)早期的简单单片机里(譬如51单片机)是没有专用的PWM定时器的,那时候我们需要自己结合GPIO和定时器模块来手工生产PWM波形(流程是这样:先将GPIO引脚电平拉高、同时启动定时器定Tduty时间,时间到了在isr中将电平拉低,然后定时T(1-duty)后再次启动定时器,然后时间到了后在isr中将电平拉高,然后再定时T*duty时间再次启动定时器····如此循环即可得到周期为T,占空比为duty的PWM波形)。
(3)后来因为定时器经常和PWM产生纠结一起,所以设计SoC的时候就直接把定时器和一个GPIO引脚内部绑定起来了,然后在定时器内部给我们设置了PWM产生的机制,可以更方便的利用定时器产生PWM波形。此时我们利用PWM定时器来产生PWM波形再不用中断了。绑定了之后坏处就是GPIO引脚是固定的、死板的、不能随便换的;好处是不用进入中断isr中,直接可以生成PWM。
(4)在S5PV210中,PWM波形产生有2个寄存器很关键,一个是TCNTB、一个是TCMPB。其中,TCNTB决定了PWM波形的周期,TCMPB决定了PWM波形的占空比。
(5)最终生成的PWM波形的周期是:TCNTB×时钟周期(PCLK_PSYS经过两极分频后得到的时钟周期)。注意这个周期是PWM中高电平+低电平的总时间,不是其中之一。
(6)最终生成的PWM波形的占空比是:TCMPB/TCNTB
输出电平翻转器
(1)PWM定时器可以规定:当TCNT>TCMPB时为高电平,当TCNT<TCMPB时为低电平。也可以规定:当TCNT>TCMPB时为低电平,当TCNT<TCMPB时为高电平。在这两种规定下,计算时TCMP寄存器的值会变化。
(2)基于上面讲的,当duty从30%变到70%时,我们TCMPB寄存器中的值就要改(譬如TCNTB中是300时,TCMPB就要从210变化到90)。这样的改变可以满足需要,但是计算有点麻烦。于是乎210的PWM定时器帮我们提供了一个友好的工具叫做电平翻转器。
(3)电平翻转器在电路上的实质就是一个电平取反的部件,在编程上反映为一个寄存器位。写0就关闭输出电平反转,写1就开启输出电平反转。开启后和开启前输出电平刚好高低反转。(输出电平一反转30%的duty就变成70%了)
死区生成器
(1)PWM有一个应用就是用在功率电路中用来对交流电压进行整流。整流时2路整流分别在正电平和负电平时导通工作,不能同时导通(同时导通会直接短路,瞬间的同时导通都会导致电路烧毁)。大功率的开关电源、逆变器等设备广泛使用了整流技术。特别是逆变器,用SoC的GPIO输出的PWM波形来分别驱动2路整流的IGBT。
(2)PWM波形不能同时高或低,因为会短路。但是实际电路是不理想的,不可能同时上升/下降沿,所以比较安全的做法是留死区。
(3)死区这东西离不了也多不了。死区少了容易短路,死区多了控制精度低了不利于产品性能的提升。
(4)S5PV210给大家提供了自带的死区生成器,只要开启死区生成器,生产出来的PWM波形就自带了死区控制功能,用户不用再自己去操心死区问题。
(5)大部分人工作是用不到这个的,直接关掉死区生成器即可。
1.6 蜂鸣器PWM编程实践
1.6.1 蜂鸣器的工作原理
(1)蜂鸣器里面有2个金属片,离的很紧但没挨着;没电的时候两个片在弹簧本身张力作用下分开彼此平行;有电的时候两边分别充电,在异性电荷的吸力作用下两个片挨着;
(2)我们只要以快速的频率给蜂鸣器的正负极:供电、断电。进行这样的循环,蜂鸣器的两个弹簧片就会挨着分开挨着分开···形成敲击,发出声音。
(3)因为人的耳朵能听见的声音频率有限制(20Hz-20000Hz),我们做实验时一般给个2KHz的频率,大部分人都能听到。
(4)频率高低会影响声音的音频,一般是音频越低声音听起来越低沉、音频越高听起来越尖锐。
(5)根据以上的分析,可以看出,只要用PWM波形的电压信号来驱动蜂鸣器,把PWM波形的周期T设置为要发出的声音信号的1/频率即可;PWM的占空比只要确保能驱动蜂鸣器即可(驱动能力问题,一般引脚驱动能力都不够,所以蜂鸣器会额外用三极管来放大流来供电)。
1.6.2 蜂鸣器原理图
分析原理图可知控制控制蜂鸣器工作的GPIO口为:GPD0_2 ,PWM的输出为XpwmTOUT2
结合PWM定时器规则和蜂鸣器原理图,编程流程大致如下:
1.重要寄存器的设定
TCFG0 : 8bit 的预分频器
TCFG1 :5个通道的MUX(实质为分频器)
TCON :定时器控制寄存器
TCNTBx :用于计数的计数器
TCMPBx :对比寄存器,用来实现占空比的的设定
1.6.3 编程实践
buzzer.c
#define GPD0CON 0xE02000A0
#define TCFG0 0xE2500000
#define TCFG1 0xE2500004
#define CON 0xE2500008
#define TCNTB2 0xE2500024
#define TCMPB2 0xE2500028
#define rGPD0CON (*(volatile unsigned int *)GPD0CON)
#define rTCFG0 (*(volatile unsigned int *)TCFG0)
#define rTCFG1 (*(volatile unsigned int *)TCFG1)
#define rCON (*(volatile unsigned int *)CON)
#define rTCNTB2 (*(volatile unsigned int *)TCNTB2)
#define rTCMPB2 (*(volatile unsigned int *)TCMPB2)
void buzzer_init (void)
{
rGPD0CON &= ~(0xf<<8);
rGPD0CON |= (2<<8); //配置GPD0 的模式为 TOUT_2
rTCFG0 &= ~(0xff<<8);
rTCFG0 |= (65<<8); //配置预分频的参数prescaler value 为65
rTCFG1 &= ~(0x0f<<8);
rTCFG1 |= (1<<8); //配置分频器为1/2
rCON |= (1<<15); //配置为reload
rTCNTB2 = 300; //计数器的初值 设为300
rTCMPB2 = 150; //占空比设为50%
rCON |=(1<<13); //第一次进行手动装载
rCON &=~(1<<13); //第一次装载之后,以后就不需要了
rCON |=(1<<12); //开始timer
}
二.看门狗定时器
2.1 什么是看门狗
(1)看门狗定时器和普通的定时器并无本质区别。定时器可以设定一个时间,在这个时间完成之前定时器不断计时,时间到的时候定时器会复位CPU(重启系统)。
(2)系统正常工作的时候当然不希望被重启,但是系统受到干扰、极端环境等可能会产生异常工作或者不工作,这种状态可能会造成不良影响(至少是不工作),此时解决方案就是重启系统。
(3)普通设备重启不是问题,但是有些设备人工重启存在困难。这时候我们希望系统能够自己检验自己是否已经跑飞,并且在意识到自己跑飞的时候,可以很快的(几个ms或者更短)自我重启。这个功能就要靠看门狗定时器来实现。
(4)典型应用的情景是:我们在应用程序中打开看门狗设备,初始化好给它一个时间,然后应用程序使用一个线程来喂狗,这个线程的执行时间安全短于看门狗的复位时间。当系统(或者应用程序)异常后,喂狗线程自然就不工作了,然后到时候看门狗就会复位。
(5)补充:实战中有时候为了绝对的可靠,我们并不会用SoC中自带的看门狗,而是使用专门的外置的看门狗芯片来实现看门狗。
2.2 S5PV210看门狗定时器的结构框图
PCLK 通过预分频和分频器产生对应的时钟频率,再通过WTCNT计数器控制CPU产生中断或者复位信号。
2.3 看门狗定时器的重要寄存器
WTCON 0xE2700000
WTDAT 0xE2700004
WTCNT 0xE2700008
WTCLRINT 0xE270000C
2.4 编程实践
任务:使用中断控制使WDT产生中断信号
wdt_interrupt.c
#include "stdio.h"
#include "main.h"
#define WTCON 0xE2700000
#define WTDAT 0xE2700004
#define WTCNT 0xE2700008
#define WTCLRINT 0xE270000C
#define rWTCON (*(volatile unsigned int*)WTCON)
#define rWTDAT (*(volatile unsigned int*)WTDAT)
#define rWTCNT (*(volatile unsigned int*)WTCNT)
#define rWTCLRINT (*(volatile unsigned int*)WTCLRINT )
void wdt_init_interrupt(void)
{
rWTCON &= ~(0xFF<<8);
rWTCON |= (65<<8); //配置预分频后为1MHz
rWTCON &= ~(0x3<<3);
rWTCON |= (1<<3); //配置分频为1/32
rWTDAT = 18750000; //设置计数器的值
rWTCNT = 18750000; //重新加载
rWTCON |= (1<<2); //开中断
rWTCON |= (1<<5); //开看门狗定时器
}
//用来设定中断后具体需要做什么
void isr_wdt (void)
{
static int k =1 ;
printf("WDT_interrupt occur , k=%d\n",k++);
intc_clearvectaddr(); //完成中断后,需要清中断
rWTCLRINT = 1;
}
main.c
#include "stdio.h"
#include "int.h"
#include "main.h"
void uart_init(void);
#define WDT_INTERRUPT NUM_WDT
int main(void)
{
uart_init();
wdt_init_interrupt();
// 如果程序中要使用中断,就要调用中断初始化来初步初始化中断控制器
system_init_exception();
printf("-------------WDT interrypt test--------------");
// 绑定isr到中断控制器硬件
intc_setvectaddr(WDT_INTERRUPT, isr_wdt);
// 使能中断
intc_enable(WDT_INTERRUPT);
// 在这里加个心跳
while (1)
{
printf("A ");
delay(10000);
}
return 0;
}
实验效果
三. 实时时钟RTC
3.1 何为实时时钟
(1)real time clock,真实时间,就是所谓的xx年x月x日x时x分x秒星期x,也就是当下实时系统的时间。
(2)RTC是SoC中一个内部外设,RTC有自己独立的晶振提供RTC时钟源(32.768KHz),内部有一些寄存器用来记录时间(年月日时分秒星期)。一般情况下为了在系统关机时时间仍然在走,还会给RTC提供一个电池供电。
3.2 S5PV210实时时钟的结构框图
通过框图我们可以知道S5PV210包含
(1)时间寄存器7个(SEC、MIN、HOUR、DATE、DAT、MON、YEAR)
(2)闹钟发生器 (alarm)
(3)闰年发生器(Leap year)
(4)1Hz的时钟频率
3.3 RTC的重要寄存器
如下图所示,主要有如下几类
(1)INTP 中断挂起寄存器
(2)RTCCON RTC控制寄存器
(3)RTCALM ALMxxx 闹钟功能有关的寄存器
(4)BCDxxx 时间寄存器
3.4 BCD码
(1)RTC中所有的时间(年月日时分秒星期,包括闹钟)都是用BCD码编码的。
(2)BCD码本质上是对数字的一种编码。用来解决这种问题:由56得到0x56(或者反过来)。也就是说我们希望十进制的56可以被编码成56(这里的56不是十进制56,而是两个数字5和6).
(3)BCD码的作用在于可以将十进制数拆成组成这个十进制数的各个数字的编码,变成编码后就没有位数的限制了。譬如我有一个很大的数123456789123456789,如果这个数纯粹当数字肯定超出了int的范围,计算机无法直接处理。要想让计算机处理这个数,计算机首先得能表达这个数,表达的方式就是先把这个数转成对应的BCD码(123456789123456789)
(4)BCD码在计算机中可以用十六进制的形式来表示。也就是说十进制的56转成BCD码后是56,在计算机中用0x56来表达(暂时存储与运算)。
(5)需要写2个函数,一个是bcd转十进制,一个是十进制转bcd。当我们要设置时间时(譬如要设置为23分),我们需要将这个23转成0x23然后再赋值给相应的寄存器BCDMIN;当我们从寄存器BCDMIN中读取一个时间时(譬如读取到的是0x59),需要将之当作BCD码转成十进制再去显示(0x59当作BCD码就是59,转成十进制就是59,所以显示就是59分)。
3.5 编程实践
3.5.1 时间的读取显示
main.c
#include "stdio.h"
#include "int.h"
#include "main.h"
void uart_init(void);
int main(void)
{
uart_init();
struct rtc_time tWrite =
{
.year = 2020,
.month = 8,
.date = 10,
.hour = 20,
.minute = 20,
.second = 3,
.day = 1,
};
printf("-------------SET RTC test--------------\n");
rtc_set_time(&tWrite);
struct rtc_time tRead;
printf("---rtc read time test---");
while (1)
{
rtc_get_time(&tRead);
printf("The time read is: %d/%d/%d/%d:%d:%d", tRead.year, tRead.month, tRead.date, tRead.hour, tRead.minute, tRead.second);
printf ("The week read is:%d",tRead.day);
// 读写之间做点延时
volatile int i, j;
for (i=0; i<10000; i++)
for (j=0; j<1000; j++);
printf("-------");
}
return 0;
}
set_rtc.c
#include "stdio.h"
#include "main.h"
#define INTP 0xE2800030
#define RTCCON 0xE2800040
#define BCDSEC 0xE2800070
#define BCDMIN 0xE2800074
#define BCDHOUR 0xE2800078
#define BCDDATE 0xE280007C
#define BCDDAY 0xE2800080
#define BCDMON 0xE2800084
#define BCDYEAR 0xE2800088
#define CURTICCNT 0xE2800090
#define rINTP (*(volatile unsigned int*)INTP)
#define rRTCCON (*(volatile unsigned int*)RTCCON)
#define rBCDSEC (*(volatile unsigned int*)BCDSEC)
#define rBCDMIN (*(volatile unsigned int*)BCDMIN)
#define rBCDHOUR (*(volatile unsigned int*)BCDHOUR )
#define rBCDDATE (*(volatile unsigned int*)BCDDATE)
#define rBCDDAY (*(volatile unsigned int*)BCDDAY)
#define rBCDMON (*(volatile unsigned int*)BCDMON )
#define rBCDYEAR (*(volatile unsigned int*)BCDYEAR)
#define rCURTICCNT (*(volatile unsigned int*)CURTICCNT)
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);
}
//使用BCD编码对十进制和十六进制进行转换
static unsigned int num_2_bcd (unsigned int num)
{
return (((num / 10)<<4) | (num % 10));
}
static unsigned int bcd_2_num (unsigned int bcd)
{
return (((bcd & 0xf0)>>4)*10 + (bcd & (0x0f)));
}
main.h
struct rtc_time
{
unsigned int year;
unsigned int month;
unsigned int date;
unsigned int hour;
unsigned int minute;
unsigned int second;
unsigned int day;
};
void rtc_get_time(struct rtc_time *p);
void rtc_set_time(const struct rtc_time *p);
static unsigned int num_2_bcd (unsigned int num);
static unsigned int bcd_2_num (unsigned int bcd);
实践效果
3.5.2 闹钟的实现
main.c
#include "stdio.h"
#include "int.h"
#include "main.h"
void uart_init(void);
#define ALERM_INTERRUPT NUM_RTC_ALARM // alarm
int main(void)
{
uart_init();
alarm_interrupt_init(); //alarm中断初始化
system_init_exception(); //如果程序中要使用中断,就要调用中断初始化来初步初始化中断控制器
// 绑定isr到中断控制器硬件
intc_setvectaddr(ALERM_INTERRUPT, isr_alarm);
// 使能中断
intc_enable(ALERM_INTERRUPT);
//设定闹钟的时间的结构体
struct alarm_time tAlarm =
{
.year = 2020,
.month = 8,
.date = 10,
.hour = 20,
.minute = 21,
.second = 0,
.day = 1,
};
alarm_set_time(&tAlarm); //设定闹钟函数
struct rtc_time tWrite =
{
.year = 2020,
.month = 8,
.date = 10,
.hour = 20,
.minute = 20,
.second = 3,
.day = 1,
};
printf("-------------SET RTC test--------------\n");
rtc_set_time(&tWrite);
struct rtc_time tRead;
printf("---rtc read time test---");
while (1)
{
rtc_get_time(&tRead);
printf("The time read is: %d/%d/%d/%d:%d:%d", tRead.year, tRead.month, tRead.date, tRead.hour, tRead.minute, tRead.second);
printf ("The week read is:%d",tRead.day);
// 读写之间做点延时
volatile int i, j;
for (i=0; i<10000; i++)
for (j=0; j<1000; j++);
printf("-------");
}
return 0;
}
alarm_interrupt.c
#include "stdio.h"
#include "main.h"
//设定时间的宏定义
#define INTP 0xE2800030
#define RTCCON 0xE2800040
#define BCDSEC 0xE2800070
#define BCDMIN 0xE2800074
#define BCDHOUR 0xE2800078
#define BCDDATE 0xE280007C
#define BCDDAY 0xE2800080
#define BCDMON 0xE2800084
#define BCDYEAR 0xE2800088
#define CURTICCNT 0xE2800090
#define rINTP (*(volatile unsigned int*)INTP)
#define rRTCCON (*(volatile unsigned int*)RTCCON)
#define rBCDSEC (*(volatile unsigned int*)BCDSEC)
#define rBCDMIN (*(volatile unsigned int*)BCDMIN)
#define rBCDHOUR (*(volatile unsigned int*)BCDHOUR )
#define rBCDDATE (*(volatile unsigned int*)BCDDATE)
#define rBCDDAY (*(volatile unsigned int*)BCDDAY)
#define rBCDMON (*(volatile unsigned int*)BCDMON )
#define rBCDYEAR (*(volatile unsigned int*)BCDYEAR)
//设置闹钟的宏定义
#define INTP 0xE2800030
#define RTCCON 0xE2800040
#define RTCALM 0xE2800050
#define ALMSEC 0xE2800054
#define ALMMIN 0xE2800058
#define ALMHOUR 0xE280005C
#define ALMDAY 0xE2800060
#define ALMMON 0xE2800064
#define ALMYEAR 0xE2800068
#define rINTP (*(volatile unsigned int*)INTP)
#define rRTCCON (*(volatile unsigned int*)RTCCON)
#define rRTCALM (*(volatile unsigned int*)RTCALM)
#define rALMSEC (*(volatile unsigned int*)ALMSEC)
#define rALMMIN (*(volatile unsigned int*)ALMMIN )
#define rALMHOUR (*(volatile unsigned int*)ALMHOUR)
#define rALMDAY (*(volatile unsigned int*)ALMDAY)
#define rALMMON (*(volatile unsigned int*)ALMMON )
#define rALMYEAR (*(volatile unsigned int*)ALMYEAR)
//闹钟中断控制器设置
void alarm_interrupt_init(void)
{
rRTCALM &= ~(0x7);
rRTCALM |= (7<<0); //设定闹钟的时分秒开启
rRTCALM |= 1<<6; //Enables Alarm global
}
//闹钟中断时,具体需要做什么
void isr_alarm (void)
{
printf("**********It's time to get up*******\n");
rINTP |= (1<<1);
intc_clearvectaddr(); //清中断
}
void alarm_set_time(const struct alarm_time *p)
{
//写RTC时间寄存器
rALMYEAR = num_2_bcd(p->year - 2000);
rALMMON = num_2_bcd(p->month);
rALMDAY = num_2_bcd(p->date);
rALMHOUR = num_2_bcd(p->hour);
rALMMIN = num_2_bcd(p->minute);
rALMSEC = num_2_bcd(p->second);
}
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);
}
//使用BCD编码对十进制和十六进制进行转换
static unsigned int num_2_bcd (unsigned int num)
{
return (((num / 10)<<4) | (num % 10));
}
static unsigned int bcd_2_num (unsigned int bcd)
{
return (((bcd & 0xf0)>>4)*10 + (bcd & (0x0f)));
}
程序实现
- C语言结构体学习
注:以上内容来自朱老师物联网裸机课程课件
本文地址:https://blog.csdn.net/qq_44034198/article/details/107874398