您的位置:首页 > 其它

S5PV210系列 (裸机十一)之 定时器、看门狗和RTC(二)

2017-05-20 15:34 393 查看

蜂鸣器和PWM定时器编程实践

蜂鸣器的工作原理

(1)蜂鸣器里面有2个金属片,离的很紧但没挨着;没电的时候两个片在弹簧本身张力作用下分开彼此平行;有电的时候两边分别充电,在异性电荷的吸力作用下两个片挨着;

(2)我们只要以快速的频率给蜂鸣器的正负极:供电、断电。进行这样的循环,蜂鸣器的两个弹簧片就会挨着分开挨着分开···形成敲击,发出声音。

(3)因为人的耳朵能听见的声音频率有限制(20Hz-20000Hz),我们做实验时一般给个2KHz的频率,大部分人都能听到(听不到的就就近医院处理)。

(4)频率高低会影响声音的音频,一般是音频越低声音听起来越低沉、音频越高听起来越尖锐。

(5)根据以上的分析,可以看出,只要用PWM波形的电压信号来驱动蜂鸣器,把PWM波形的周期T设置为要发出的声音信号的1/频率即可;PWM的占空比只要确保能驱动蜂鸣器即可(驱动能力问题,一般引脚驱动能力都不够,所以蜂鸣器会额外用三极管来放大流来供电)。

原理图和硬件信息

(1)查阅原理图可知,开发板底板上的蜂鸣器通过GPD0_2(XpwmTOUT2)引脚连接在SoC上。





(2)GPD0_2引脚通过限流电阻接在三极管基极上,引脚有电蜂鸣器就会有电(三极管导通);引脚没电蜂鸣器就会没电(三极管关闭)。这些都是硬件问题,软件工程师不用管,软件工程师只要写程序控制GPD0_2引脚的电平产生PWM波形即可。

(3)GPD0CON(0xE02000A0),要把bit8〜bit11设置为0b0010(功能选择为TOUT_2,就是把这个引脚设置为PWM输出功能)



(4)从GPD0_2引脚可以反推出使用的是timer2这个PWM定时器。

PWM定时器的主要寄存器详解

(1)相关的寄存器有TCFG0、TCFG1、TCON、TCNTB2、TCMPB2、TCNTO2



#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)

// 初始化PWM timer2,使其输出PWM波形:频率是2KHz、duty为50%
void timer2_pwm_init(void)
{
// 设置GPD0_2引脚,将其配置为XpwmTOUT_2
rGPD0CON &= ~(0xf<<8); // 清零
rGPD0CON |= (2<<8);

// 设置PWM定时器的一干寄存器,使其工作
rTCFG0 &= ~(0xff<<8);
rTCFG0 |= (65<<8);          // prescaler1 = 65, 预分频后频率为1MHz

rTCFG1 &= ~(0x0f<<8);
rTCFG1 |= (1<<8);           // MUX2设置为1/2,分频后时钟周期为500KHz
// 时钟设置好,我们的时钟频率是500KHz,对应的时钟周期是2us。也就是说每隔2us
// 计一次数。如果要定的时间是x,则TCNTB中应该写入x/2us

rCON |= (1<<15);        // 使能auto-reload,反复定时才能发出PWM波形
//rTCNTB2 = 250;            // 0.5ms/2us = 500us/2us = 250
//rTCMPB2 = 125;            // duty = 50%

rTCNTB2 = 50;
rTCMPB2 = 25;

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

rCON |= (1<<12);        // 开timer2定时器。要先把其他都设置好才能开定时器
}


看门狗定时器

什么是看门狗、有什么用

(1)看门狗定时器和普通的定时器并无本质区别。定时器可以设定一个时间,在这个时间完成之前定时器不断计时,时间到的时候定时器会复位CPU(重启系统)。

(2)系统正常工作的时候当然不希望被重启,但是系统受到干扰、极端环境等可能会产生异常工作或者不工作,这种状态可能会造成不良影响(至少是不工作),此时解决方案就是重启系统。

(3)普通设备重启不是问题,但是有些设备人工重启存在困难。这时候我们希望系统能够自己检验自己是否已经跑飞,并且在意识到自己跑飞的时候,可以很快的(几个ms或者更短)自我重启。这个功能就要靠看门狗定时器来实现。

(4)典型应用的情景是:我们在应用程序中打开看门狗设备,初始化好给它一个时间,然后应用程序使用一个线程来喂狗,这个线程的执行时间安全短于看门狗的复位时间。当系统(或者应用程序)异常后,喂狗线程自然就不工作了,然后到时候看门狗就会复位。

(5)补充:实战中有时候为了绝对的可靠,我们并不会用 SoC 中自带的看门狗,而是使用专门的外置的看门狗芯片来实现看门狗。

S5PV210 看门狗定时器的结构框图

(1) PCLK_PSYS 经过两级分频后生成 WDT(watchdog timer)的时钟周期,然后把要定的时间写到 WTDAT 寄存器中,刷到 WTCNT 寄存器中去减 1,减到 0 时(定时时间到)产生复位信号或中断信号。

(2)典型应用中是配置为产生复位信号,我们应该在WTCNT寄存器减到0之前给WTDAT寄存器中重新写值以喂狗。

看门狗定时器的主要寄存器

WTCON WTDAT WTCNT WTCLRINT



看门狗定时器的编程实践

#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)

// 初始化WDT使之可以产生中断
void wdt_init_interrupt(void)
{
// 第一步,设置好预分频器和分频器,得到时钟周期是 128us
rWTCON &= ~(0xff<<8);
rWTCON |= (65<<8);              // 1MHz

rWTCON &= ~(3<<3);
rWTCON |= (3<<3);               // 1/128 MHz, T = 128us

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

// 第三步,设置定时时间
// WDT定时计数个数,最终定时时间为这里的值×时钟周期
//rWTDAT = 10000;                   // 定时1.28s
//rWTCNT = 10000;                   // 定时1.28s

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

// 第四步,先把所有寄存器都设置好之后,再去开看门狗
rWTCON |= (1<<5);               // enable wdt
}

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

// 清中断
intc_clearvectaddr();
rWTCLRINT = 1;
}


实时时钟RTC

何为实时时钟

(1) real time clock,真实时间,就是所谓的xx年x月x日x时x分x秒星期x

(2) RTC 是 SoC 中一个内部外设,RTC 有自己独立的晶振提供 RTC 时钟源( 32.768KHz ),内部有一些寄存器用来记录时间(年月日时分秒星期)。

一般情况下为了在系统关机时时间仍然在走,还会给 RTC 提供一个电池供电。

S5PV210 实时时钟的结构框图

(1)时间寄存器 7 个

(2)闹钟发生器



闹钟发生器

(1)可以定闹钟时间,到时间会产生 RTC alarm interrupt,通知系统闹钟定时到了。

(2)闹钟定时是定的时间点,而 timer 定时是定的时间段。

S5PV210 实时时钟的主要寄存器

(1)INTP 中断挂起寄存器

(2)RTCCON RTC 控制寄存器

(3)RTCALM ALMxxx 闹钟功能有关的寄存器

(4)BCDxxx 时间寄存器

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 分)。

RTC编程实战

设置时间与读取显示时间

(1)为了安全,默认情况下 RTC 读写是禁止的,此时读写 RTC 的时间是不允许的;当我们要更改 RTC 时间时,应该先打开 RTC 的读写开关,然后再进行读写操作,操作完了后立即关闭读写开关。

(2)读写 RTC 寄存器时,一定要注意 BCD 码和 十进制 之间的转换。

(3)年的问题。S5PV210 中做了个设定,BCDYEAR 寄存器存的并不是完整的年数(譬如今年 2015 年),而是基于 2000 年的偏移量来存储的,譬如今年 2015 年实际存的就是 15(2015-2000).还有些 RTC 芯片是以 1970 年(貌似)为基点来记录的。

// rtc.c
struct rtc_time
{
unsigned int year;
unsigned int month;
unsigned int date;          // 几号
unsigned int hour;
unsigned int minute;
unsigned int second;
unsigned int day;           // 星期几
};


int main(void)
{
uart_init();

system_init_exception();
rtc_set_alarm();

intc_setvectaddr(NUM_RTC_ALARM, isr_rtc_alarm);
intc_enable(NUM_RTC_ALARM);

struct rtc_time tRead;

while (1)
{
rtc_get_time(&tRead);
printf("The time read is: %d.", tRead.second);

volatile int i, j;
for (i=0; i<10000; i++)
for (j=0; j<1000; j++);
printf("-------");
}


#include "main.h"

#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)))

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

void rtc_set_alarm(void)
{
rALMSEC = num_2_bcd(23);
rRTCALM |= 1<<0;
rRTCALM |= 1<<6;
}

void isr_rtc_alarm(void)
{
static int i = 0;
printf("rtc alarm, i = %d...", i++);

rINTP |= (1<<1);
intc_clearvectaddr();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息