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

来源:互联网 发布:大宗商品数据库 编辑:程序博客网 时间:2024/04/26 20:05
一、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_2XpwmTOUT2)引脚
人的耳朵能听见的声音频率有限制(20Hz-2KHz
GPD0CON(0xE02000A0),要把bit8bit11设置为0b0010(功能选择为TOUT_2,就是把这个引脚设置为PWM输出功能)
GPD0_2引脚可以反推出使用的是timer2这个PWM定时器。
//设置GPD0_2引脚,将其配置为XpwmTOUT_2rGPD0CON &=~(0xf<<8);rGPD0CON |=(0x2<<8);        //设置预分频器参数为65,则预分频系数为66,1MHZrTCFG0 &=~(0xff<<8);rTCFG0 |=(65<<8);       //设置分频器参数为1,分频系数为1/2,500KHZrTCFG1 &= ~(0x0f<<8);rTCFG1 |= (1<<8);         //设置自动重载// 计一次数。如果要定的时间是x,则TCNTB中应该写入x/2usrCON |= (1<<15);rTCNTB2 = 250;// 0.5ms/2us = 500us/2us = 250rTCMPB2 = 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经过两级分频后生成WDTwatchdog timer)的时钟周期,然后把要定的时间写到WTDAT寄存器中,刷到WTCNT寄存器中去减1,减到0时(定时时间到)产生复位信号或中断信号。
(2)典型应用中是配置为产生复位信号,我们应该在WTCNT寄存器减到0之前给WTDAT寄存器中重新写值以喂狗。
void wdt_interrupt_init(void){//设置预分频,得到时钟周期是128usWTCON &= ~(0xFF<<8);WTCON |= (65<<8);//1Mhz//设置分频WTCON &= ~(3<<3);WTCON |= (3<<3);//1/128// 第二步,设置中断和复位信号的使能或禁止rWTCON |= (1<<2);// enable wdt interruptrWTCON &= ~(1<<0);// disable wdt reset//设置定时时间//其实WTDAT中的值不会自动刷到WTCNT中去,如果不显式设置WTCON中的值,它的值就是// 默认值,然后以这个默认值开始计数,所以这个时间比较久。如果我们自己显式的// 设置了WTCNT和WTDAT一样的值,则第一次的定时值就和后面的一样了。rWTDAT = 1000;// 定时0.128srWTCNT = 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,而是两个数字56.
(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年实际存的就是152015-2000.还有些RTC芯片是以1970年(貌似)为基点来记录的。
主要函数:RTC主要寄存器的读写操作,即基地址+偏移量地址访问的方式



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