十一.ARM裸机学习之定时器、看门狗和RTC时钟

一、PWM 定时器
1. S5PV210内部共有5个32bit的PWM定时器。PWM定时器可以生成内部中断。PWM定时器0、1、2、3具有PWM功能,可以驱动外部I/O信号。PWM定时器4是一个无外部引脚的内部定时器。PWM 定时器使用 PCLK_PSYS 作为时钟源。
2. 每个定时器有一个由定时器时钟驱动的32位递减计数器。递减计数器的初始值是由TCNTBn自动装载而获得的。如果递减计数器减到 0 时,定时器发出中断请求通知CPU定时器操作已经完成,当定时器递减计数器到达 0,相应的 TCNTBn的值也会自动的装载到递减计数器中以继续下一次循环操作。 在定时器正在运行模式下通过对TCON的定时器使能位清零,则TCNTBn的值不会自动装载到计数器中。TCMPBn 寄存器的值用于脉宽调制功能(PWM)。当递减计数器的值和定时器控制逻辑单元中的比较寄存器的值匹配时,定时器控制单元会改变输出电平。因此,比较寄存器的值决定了PWM的占空比。当定时器使能,定时器计数存寄存器(TCNTBn)得到一个被装载到递减计数器中的初始值。定时器比较缓存寄存器(TCMPBn)有一个被装载到比较寄存器中用来和递减计数器的值作比较的初始值。 TCNTBn和TCMPBn双缓存特点使得当频率和负荷发生改变时,定时器生成一个稳定的输出
主要控制寄存器:
   TCFG0: 预分频器参数设置,范围为0-255,定时器0和1,用 Prescaler0,定时器2,3,4用Prescaler 1,预分频系数为1-256
     TCFG1 :分频器参数设置,MUX开关,1/1,1/2,1/4,1/8,1/16
     TCON: 定时器的设置
     TCNTBn: 定时计数寄存器
     TCMPBn: 占空比计数寄存器
     TCNTOn: 观察寄存器
1.1 PWM 之蜂鸣器实验
蜂鸣器通过 GPD0_2 XpwmTOUT2 )引脚
人的耳朵能听见的声音频率有限制( 20Hz-2KHz
GPD0CON(0xE02000A0) ,要把 bit8 bit11 设置为 0b0010 (功能选择为 TOUT_2 ,就是把这个引脚设置为 PWM 输出功能)
GPD0_2 引脚可以反推出使用的是 timer2 这个 PWM 定时器。
//设置GPD0_2引脚,将其配置为XpwmTOUT_2
	rGPD0CON &=~(0xf<<8);
	rGPD0CON |=(0x2<<8);
	
        //设置预分频器参数为65,则预分频系数为66,1MHZ
	rTCFG0 &=~(0xff<<8);
	rTCFG0 |=(65<<8);
	
       //设置分频器参数为1,分频系数为1/2,500KHZ
	rTCFG1 &= ~(0x0f<<8);
	rTCFG1 |= (1<<8);			
	
         //设置自动重载
	// 计一次数。如果要定的时间是x,则TCNTB中应该写入x/2us
	rCON |= (1<<15);		
	rTCNTB2 = 250;	// 0.5ms/2us = 500us/2us = 250
	rTCMPB2 = 125;	// duty = 50%

   // 第一次需要手工将TCNTB中的值刷新到TCNT中去,以后就可以auto-reload了
	rCON |= (1<<13);		// 打开自动刷新功能
	rCON &= ~(1<<13);		// 关闭自动刷新功能
	
         //打开timer2
        rCON |= (1<<12);		
二.看门狗定时器

看门狗定时器相当于一个普通的 16bit 的定时器,看门狗定时器可以产生 reset信号
相关寄存器:
    WTCON:选择中断、复位模式,时钟,如果使用看门狗提供定时器功能,打开中断,关闭复位
    WTDAT:看门狗递减计数器的重载值,初始化时不能自动重载
    WTCNT:看门狗递减计数器的初始值,初始化时不能从WTDAT自动加载
    WTCLRINT:写任意值,清除中断 
(1)PCLK_PSYS 经过两级分频后生成 WDT watchdog timer )的时钟周期,然后把要定的时间写到 WTDAT 寄存器中,刷到 WTCNT 寄存器中去减 1 ,减到 0 时(定时时间到)产生复位信号或中断信号。
(2) 典型应用中是配置为产生复位信号,我们应该在 WTCNT 寄存器减到 0 之前给 WTDAT 寄存器中重新写值以喂狗。
void wdt_interrupt_init(void)
{
//设置预分频,得到时钟周期是128us
WTCON &= ~(0xFF<<8);
WTCON |= (65<<8);//1Mhz
//设置分频
WTCON &= ~(3<<3);
WTCON |= (3<<3);//1/128

// 第二步,设置中断和复位信号的使能或禁止
rWTCON |= (1<<2);// enable wdt interrupt
rWTCON &= ~(1<<0);// disable wdt reset

//设置定时时间
//其实WTDAT中的值不会自动刷到WTCNT中去,如果不显式设置WTCON中的值,它的值就是
// 默认值,然后以这个默认值开始计数,所以这个时间比较久。如果我们自己显式的
// 设置了WTCNT和WTDAT一样的值,则第一次的定时值就和后面的一样了。
rWTDAT = 1000;// 定时0.128s
rWTCNT = 1000;// 定时0.128s

//最后打开看门狗
WTCON |= (1<<5);}

//中断服务程序
void isr_wdt(void)
{
static int i = 0;
// 看门狗定时器时间到了时候应该做的有意义的事情
printf("wdt interrupt, i = %d...", i++);
// 清中断
intc_clearvectaddr();
rWTCLRINT = 1;
}

三.实时时钟RTC

实时时钟在系统电源关闭后使用备份电源。实时时钟使用32.768 kHz的外部晶体振荡器工作,具备闹钟功能。
1.主要控制寄存器:
(1)INTP 中断挂起寄存器
(2)RTCCON     RTC 控制寄存器
(3)RTCALM ALMxxx    闹钟功能有关的寄存器
(4)BCDxxx   时间寄存器
2.实时时钟RTC的闹钟编程说明
(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 分)。
设置时间与读取显示时间
(1) 为了安全,默认情况下 RTC 读写是禁止的,此时读写 RTC 的时间是不允许的;当我们 要更改 RTC 时间时,应该先打开 RTC 的读写开关,然后再进行读写操作,操作完了后立即关闭读写开关。
(2) 读写 RTC 寄存器时,一定要注意 BCD 码和十进制之间的转换。
(3) 年的问题。 S5PV210 中做了个设定, BCDYEAR 寄存器存的并不是完整的年数(譬如今年 2015 年),而是基于 2000 年的偏移量来存储的,譬如今年 2015 年实际存的就是 15 2015-2000 . 还有些 RTC 芯片是以 1970 年(貌似)为基点来记录的。
主要函数:RTC主要寄存器的读写操作,即基地址+偏移量地址访问的方式



// 函数功能:把十进制num转成bcd码,譬如把56转成0x56
static unsigned int num_2_bcd(unsigned int num)
{
	// 第一步,把56拆分成5和6 
	// 第二步,把5和6组合成0x56
	return (((num / 10)<<4) | (num % 10));
}

// 函数功能:把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)));
}

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);
}




猜你喜欢

转载自blog.csdn.net/wangweijundeqq/article/details/78776517