在线时间6059 小时
UID3426478
注册时间2017-9-25
NXP金币5450
TA的每日心情 | 衰 4 天前 |
---|
签到天数: 1481 天 [LV.10]以坛为家III
超级版主
- 积分
- 92877
- 最后登录
- 2024-4-30
|
LPC系列控制的CTIMER,可以作为常见的定时器,或者计数器。在作为定时器的时候,常见的应用是定时执行某项操作,例如周期性的CAN报文发送,更新显示等,也可以通过对MATCH针脚的控制,产生不同周期的PWM波形,这个在很多开发板的BSP中有示例,可参考Keil\ARM\PACK\NXP\LPCXpressoXXX_BSP\X.X.X\driver_examples\ctimer中的示例。这两个示例在各种开发板中都有,连入门级的LPC8xx都有,更不用提LPC54xx,LPC55xx的开发板了,相信大家看一下都能够懂。
不过,官方示例中并没有演示CTIMER的捕获(CAP)功能,论坛里对这个的讨论也不是太多,所以这一块儿就需要拿着用户手册好好看一下,自己摸索着比划比划。
作为计数器的时候,可以配置为两种不同的方式,通过CCR寄存器中的CMODE,可以选用计时方式,也可以选择计数方式,两者的区别是,前一种模式下,TC寄存器是根据APB脉冲增加;而后一种模式下,TC根据CAP的行为(上升沿、下降沿或者双边沿)递增,这种模式可以说是真正意义上的计数器了。
但是前一种模式也不是说用不上,通过对文档的阅读和理解,并自己动手测试,写了一个对PWM波的解调(Pulse Width Demodulation),计算PWM波的频率(周期)和占空比。
这里选用LPC51U68作为测试的板子。
先看看CAP针脚,众所周知,LPC的很多针脚都可以被配置为CTIMER的MAT或者CAP,这个时候主要是照顾开发板,看看那个针脚做了排针或者排母,方便使用,瞄了一会原理图和针脚定义,就它了:
没错,就是P1.16这个针脚,位于J1的19号位,CTIMER0也就自然而然了。而且这个针脚还真不错,可以做输出(MAT),也可以做输入(CAP)
然后是配置CTIMER0,这里直接撸寄存器:
- void XDCT0_Init()
- {
- //enable clock
- SYSCON->AHBCLKCTRLSET[1]=(1U<<26);
-
- //Reset
- SYSCON->PRESETCTRLSET[1]=(1U<<26);
- SYSCON->PRESETCTRLCLR[1]=(1U<<26);
- //pin config
- //Enable clock of the IOCON
- SYSCON->AHBCLKCTRL[0]|=(1U<<13);
- //P1_16 FUNC3 as CTIMER0_CAP0 P1.16@J1_19
- IOCON->PIO[1][16]=0x03|(1U<<7)|(1U<<8);
- //disable clock of the IOCON when finish setting to save power
- SYSCON->AHBCLKCTRLCLR[0]=(1U<<13);
- //Using Timer Mode
- //clear TC when CH0 Rising Edge
- CTIMER0->CTCR=0x00|(1U<<4);
-
- //R/F Edge of capture CH0, and generate INT
- CTIMER0->CCR=(1U<<0)|(1U<<1)|(1U<<2);
-
- NVIC_EnableIRQ(CTIMER0_IRQn);
- }
复制代码 配置好了以后,CTIMER0会在CAP0(也就是P1.16针脚)采集到上升沿和下降沿的时候都产生中断,那我就在中断处理函数中加入处理:
- void CTIMER0_IRQHandler()
- {
- CTIMER0->IR=(1U<<4);//clear Interrupt flag of CH0 event
- GPIO_PinRead(GPIO, 1, 16)?(a=CTIMER0->CR[0]):(b=CTIMER0->CR[0]);
- }
复制代码 通常,中断处理函数应该尽可能短小简介,所以这里面只干了两件事:一是清除中断标志,个人习惯是进中断就请标志,怕后面忘记了。第二件事是读CAP针脚状态,然后针脚高电平的时候把CR[0](捕获寄存器)中的值赋值给a,针脚低电平的时候把CR中的值赋值给b。本来,我约莫着中断寄存器中应该有中断来源(上升沿或者下降沿)的标志,结果搞了一圈也没找到,后来实在没办法,测试了一下GPIO的PIN高低电平是可以读取的,所以采用了这种方式去识别到底是高电平还是低电平产生的中断。
此外,在初始化配置的时候,我配置为每个上升沿到来时,把TC中的值载入CR[0],并清空TC寄存器,这样有两个好处:
一个是后面计算的时候方便一些,因为每次这么一清空,那么CR[0]中的值就是周期了,而不用通过这次的值去减去上次的值;
另外一个好处就是,TC也会溢出,这种溢出没有任何征兆,不会产生任何中断,所以只要我及时清空,就不会出现溢出的问题。下降沿的时候CR[0]值记录下来,这样我就知道了高电平持续的时间,所以a就是周期,b/a就是占空比。
这里a,b是两个uint32_t的全局变量,为了在这个模块中把这两个值传递出去,又写了一个函数:
- void XDCT0_PDC(uint32_t* period_cnt,uint32_t* dc_cnt)
- {
- *period_cnt=a;
- *dc_cnt=b;
- }
复制代码
最后,在主函数里面,我们可以用比较慢的频率刷新解调结果:
- int main()
- {
- float x,y;
- uint32_t p,d;
- BootClockPLL150M();
- SysTick_Config(SystemCoreClock/1000U);
- XDUART0_Init();
- XDCT0_Init();
- XDCT0_Start();
-
- while (1)
- {
- SysTick_DelayTicks(500);
- memset(buffer,0x00,256);
- XDCT0_PWMD(&p,&d);
- x=SystemCoreClock*1.0f/p;
- y=100.0f*d/p;
- sprintf(buffer,"Freq=%d Hz\tDuty Cycle=%d%%\n",(uint32_t)(0.5+x),(uint32_t)(0.5+y));
- XDUART0_WriteString(buffer,strlen(buffer));
- }
- return 0;
- }
复制代码 接线方式很简单,把PWM输出信号与P1.16对接就OK了:
最后是测试结果:
测试了一下,在300KHz情况下还可以,误差不算太大,不过拉到1MHz直接崩了,毕竟APB频率有限。
|
|