NXP JN5169使用定时器进行PWM输出和定时功能
一、定时器介绍
1、定时器介绍
JN516x 器件有 5 个定时器:定时器0、定时器 1、定时器 2、定时器 3 和定时器 4。
注:这些定时器与唤醒定时器和节拍定时器有所不同。
定时器框图:
这些定时器提供了下面一系列的操作模式:
- 定时器模式
- 脉宽调制(PWM)模式
- 计数器模式
- 捕获模式
- Delta-Sigma 模式
但是,并非全部定时器都可以在所有模式下操作。定时器 1-4 不支持需要外部输入的模式(这些是计数器模式和捕获模式)。
2、定时器可操作的模式
JN516x 微控制器可使用下面的定时器模式:定时器、脉宽调制(PWM)、计数器、捕获和Delta-Sigma。下表对这些模式以及每个模式所需的函数(在调用 vAHI_TimerEnable( )后)进行总结。除非特别说明,所有 JN516x 定时器都支持这些模式。
模式 | 描述 | 函数 |
---|---|---|
定时器 | 源时钟用于产生一个脉冲周期,这个周期由在正脉冲边沿以前的时钟周期数和负脉冲边沿以前的时钟周期数来定义。中断可以在任一边沿或两个边沿上产生。脉冲周期在‘单次触发’模式下只产生一次,在‘重复’模式下连续产生。 | vAHI_TimerConfigureOutputs( ) vAHI_TimerStartSingleShot( ) 或 vAHI_TimerStartRepeat( ) |
PWM | PWM 模式与定时器模式相同,但在 DIO 管脚上输出的脉宽调制信号除外(这取决于所使用的特定定时器)。 | vAHI_TimerConfigureOutputs( ) vAHI_TimerStartSingleShot( ) 或 vAHI_TimerStartRepeat( ) |
计数器 | 定时器用于对作为外部时钟输入的外部输入信号边沿进行计数。定时器可以只计数上升沿或计数上升沿和下降沿。定时器 0 支持该模式,但定时器 1-4 不支持该模式。 | vAHI_TimerClockSelect( ) vAHI_TimerConfigureInputs( ) vAHI_TimerStartSingleShot( ) 或 vAHI_TimerStartRepeat( ) u16AHI_TimerReadCount( ) |
捕获 | 外部输入信号在源时钟的每个节拍上采样。捕获的结果允许计算采样信号的周期和脉宽。如果需要,可以读取结果而无需停止定时器。定时器 0 支持该模式,但定时器 1-4 不支持该模式 | vAHI_TimerConfigureInputs( ) vAHI_TimerStartCapture( ) vAHI_TimerReadCapture( ) 或 vAHI_TimerReadCaptureFreeRunning( ) |
Delta-Sigma | 定时器用作一个低速率的 DAC。转换信号在 DIO 管脚上输出(这取决于使用的特定定时器),需要简单过滤来得到模拟信号。Delta-Sigma模式可以在 NRZ 和 RTZ 这两种选择下使用。 | vAHI_TimerStartDeltaSigma( ) |
3、定时器DIO
定时器可使用特定的 DIO 管脚,如下表所示。
定时器 0 DIO[1] | 定时器 1 DIO[1] | 定时器 2 DIO[1] | 定时器 3 DIO[1] | 定时器 4 DIO[1] | 功能 |
---|---|---|---|---|---|
8 | 不使用[2] | 不使用[2] | 不使用[2] | 不使用[2] | 时钟或门控输入(在计数器模式下使用) |
9 | 不使用[2] | 不使用[2] | 不使用[2] | 不使用[2] | 捕获模式输入 |
10 | 11 | 12 | 13 | 17 | PWM 和 Delta-Sigma 模式输出 |
[1] vAHI_TimerSetLocation( )可用于将定时器 0 信号从 DIO8-10 传输到 DIO2-4 或将定时器 1-4 信号从DIO11-13 和 DIO17 传输到 DIO5-8。或者这个函数也可以用于将定时器 2 和定时器 3 信号分别放置于 DO0 和DO1(数字输出)。
[2] 定时器 1-4 没有输入。
默认情况下,与使能定时器相关的 DIO 管脚保留给定时器使用,但在定时器禁能时变为给通用输入/输出(GPIO)使用。通过调用函数vAHI_TimerDIOControl( ),分配给定时器的 DIO管脚也可以释放给 GPIO 使用。或者,函数 vAHI_TimerFineGrainDIOControl( )也可以将 DIO配置为同时给所有定时器使用,还可以单独使用这个函数来释放与定时器 0 相关的 3 个 DIO 管脚。
注:在使用 vAHI_TimerEnable( ) 使能定时器前执行上述的 DIO 配置,以消除定时器操作期间在 GPIO 上产生的干扰。
4、定时器和PWM模式
定时器模式允许定时器产生一个指定周期的矩形波形,其中这个波形开始为低电平,接着在特定的时间后变为高电平。这些时间在定时器启动时被指定(如下所示),见下面的参数:
- Time to rise(μ16Hi):这是启动定时器和(首次)低-到-高跳变之间的时钟周期数。中断可以在这个跳变时产生;
- Time to fall(μ16Lo):这是启动定时器和(首次)高-到-低跳变之间的时钟周期数(一个有效的脉冲周期)。中断可以在这个跳变时产生。
这些时间和定时器信号在下面的图中说明。
在定时器模式内有 2 个子模式,定时器使用不同的函数在这些模式下启动:
- 单次触发模式:定时器产生一个脉冲周期(如图 7.1所示),接着停止。可使用函数vAHI_TimerStartSingleShot()在该模式下启动定时器;
- 重复模式:定时器产生一连串脉冲(其中的重复率由配置的“time to fall”周期来决定,见上面的描述)。可使用函数 vAHI_TimerStartRepeat( )在该模式下启动定时器。
一旦启动,就可以使用函数 vAHI_TimerStop( )来停止定时器。
PWM(脉宽调制)模式与定时器模式相同,但在 DIO 管脚上输出产生的波形除外。这个输出可以在函数vAHI_TimerEnable( )中使能。这个输出也可以使用函数 vAHI_TimerConfigureOutputs( )翻转。
5、定时器中断
在函数 vAHI_TimerEnable( )中,定时器可以在出现下面的一种情况或两种情况时配置为产生中断:
- 在定时器输出的上升沿(低电平周期的末尾)
- 在定时器输出的下降沿(整个定时器周期的末尾)
定时器中断的处理必须包含在特殊定时器用户定义的回调函数中。这些回调函数使用各个定时器专用的注册函数来注册:
- vAHI_Timer0RegisterCallback( ) 用于定时器 0
- vAHI_Timer1RegisterCallback( ) 用于定时器 1
- vAHI_Timer2RegisterCallback( ) 用于定时器 2
- vAHI_Timer3RegisterCallback( ) 用于定时器 3
- vAHI_Timer4RegisterCallback( ) 用于定时器 4
当出现 E_AHI_DEVICE_TIMER0、E_AHI_DEVICE_TIMER1、E_AHI_DEVICE_TIMER2、E_AHI_DEVICE_TIMER3 或E_AHI_DEVICE_TIMER4 类型的中断时,会自动调用相关的回调函数。从传递给函数的位图表中可以识别中断的确切信息(上面列出的两种情况的中断)。需要注意的是,在调用回调函数之前中断将自动清除。
二、实现代码
1、PWM输出
软件模拟PWM输出IO为:DIO3
硬件PWM输出IO为: DIO13
相关宏定义
#define TIMER0 E_AHI_TIMER_0
#define TIMER1 E_AHI_TIMER_1
#define TIMER2 E_AHI_TIMER_2
#define TIMER3 E_AHI_TIMER_3
#define TIMER4 E_AHI_TIMER_4
#define TIMER_PRESCALE 3 /* 定时器预分值,TIMER_FREQUENCY = 16 / (2 ^ TIMER_PRESCALE)*/
#define TIMER_FREQUENCY 16 / (pow(2, TIMER_PRESCALE)) /* 定时器时钟 2MHz */
#define TIMER_TIMING 1000 /* 定时器定时时间 time = 1000us ,时间单位us */
#define TIMER_COUNT TIMER_FREQUENCY * TIMER_TIMING /* 定时器一个有效的脉冲周期的时钟周期数, 即定时器初值*/
#define LOW_PULSE 30 /* 低电平占比(0 - 100),为一个有效的脉冲周期内低电平所占的时钟周期数*/
#define LOW_PULSE_VALUE TIMER_COUNT * (LOW_PULSE / 100.0) /* 低电平时钟数值*/
#define PWM_FREQUENCY 1 / (TIMER_TIMING / 1000000.0) /* PWM频率,单位Hz*/
#define PWM_LED (1 << 3) //软件模拟PWM输出DIO
#define LED_ON(Led) vAHI_DioSetOutput(0, Led)
#define LED_OFF(Led) vAHI_DioSetOutput(Led, 0)
定时器初始化代码
PRIVATE void vTimerInit(void)
{
/*定时器3输出PWM*/
vAHI_TimerEnable(TIMER3, //定时器3
TIMER_PRESCALE, //8分频,定时器时钟为2MHz
TRUE, //开启上升输出(从低到高)中断
TRUE, //开启定时器周期结束时(从高到低)中断
TRUE); //启用DIO上定时器信号的输出
vAHI_Timer3RegisterCallback(vCbTimer3); //注册中断回调
/**
* 定时器初值计算方法:
* 外设时钟 fsys = 16MHz
* 定时器时钟根据vAHI_TimerEnable()函数的u8Prescale分频系数算出
* Tsys = 16 / (2 ^ u8Prescale)
* 假设u8Prescale = 3, Tsys = 16 / (2 ^ 3) = 16 / 8 = 2MHz
* 假如要定时 time = 1000us
* 定时器一个有效的脉冲周期u16Lo = time * Tsys = 1000 * 2 = 2000
* 所以vAHI_TimerStartRepeat()函数的 u16Lo = 2000 (一个有效的脉冲周期的时钟周期数)
* 假设开启了vAHI_TimerEnable()函数的定时器周期结束中断bIntPeriodEnable = TRUE,则产生一次中断的时间为 1000us
* vAHI_TimerStartRepeat()函数的 u16Hi 为一个有效的脉冲周期内低电平所占的时钟周期数
* 当 u16Hi = 1000时,即在一个1000us的脉冲周期内,低电平占500us
*/
vAHI_TimerStartRepeat(TIMER3, //定时器3
LOW_PULSE_VALUE, //一个有效的脉冲周期的低电平时钟周期数。中断可以在这个跳变时产生;
TIMER_COUNT //一个有效的脉冲周期的时钟周期总数。中断可以在这个跳变时产生。
);
}
定时器中断回调函数
PRIVATE void vCbTimer3 (uint32 u32Device, uint32 u32ItemBitmap)
{
//软件模拟PWM输出实现
if(E_AHI_DEVICE_TIMER3 == u32Device){ //定时器3中断
if(E_AHI_TIMER_RISE_MASK == u32ItemBitmap){ //上升输出(从低到高)中断
LED_OFF(PWM_LED); //输出低电平
}
else{
LED_ON(PWM_LED); //输出高电平
}
}
}
完整代码
PRIVATE void vCbTimer3 (uint32 u32Device, uint32 u32ItemBitmap);
PRIVATE void vTimerInit(void);
PRIVATE void vPWMDIOsInit(void);
PUBLIC void AppColdStart (void)
{
vAHI_WatchdogStop();
(void)u32AHI_Init();
vPWMDIOsInit();
vUartInit();
vTimerInit();
/**
* 软件模拟PWM输出IO为:DIO3
* 硬件PWM输出IO为: DIO13
* */
while (1) {
vAHI_CpuDoze(); /* Doze */
}
}
PUBLIC void AppWarmStart (void)
{
AppColdStart();
}
PRIVATE void vCbTimer3 (uint32 u32Device, uint32 u32ItemBitmap)
{
if(E_AHI_DEVICE_TIMER3 == u32Device){ //定时器3中断
if(E_AHI_TIMER_RISE_MASK == u32ItemBitmap){ //上升输出(从低到高)中断
LED_OFF(PWM_LED);
}
else{
LED_ON(PWM_LED);
}
}
}
PRIVATE void vTimerInit(void)
{
/*定时器0输出PWM*/
//vAHI_TimerSetLocation(TIMER0, TRUE, FALSE); //将定时器 0 信号从 DIO8-10 传输到 DIO2-4
vAHI_TimerEnable(TIMER3, //定时器3
TIMER_PRESCALE, //8分频,定时器时钟为2MHz
TRUE, //开启上升输出(从低到高)中断
TRUE, //开启定时器周期结束时(从高到低)中断
TRUE); //启用DIO上定时器信号的输出
vAHI_Timer3RegisterCallback(vCbTimer3); //注册中断回调
/**
* 定时器初值计算方法:
* 外设时钟 fsys = 16MHz
* 定时器时钟根据vAHI_TimerEnable()函数的u8Prescale分频系数算出
* Tsys = 16 / (2 ^ u8Prescale)
* 假设u8Prescale = 3, Tsys = 16 / (2 ^ 3) = 16 / 8 = 2MHz
* 假如要定时 time = 1000us
* 定时器一个有效的脉冲周期u16Lo = time * Tsys = 1000 * 2 = 2000
* 所以vAHI_TimerStartRepeat()函数的 u16Lo = 2000 (一个有效的脉冲周期的时钟周期数)
* 假设开启了vAHI_TimerEnable()函数的定时器周期结束中断bIntPeriodEnable = TRUE,则产生一次中断的时间为 1000us
* vAHI_TimerStartRepeat()函数的 u16Hi 为一个有效的脉冲周期内低电平所占的时钟周期数
* 当 u16Hi = 1000时,即在一个1000us的脉冲周期内,低电平占500us
*/
vAHI_TimerStartRepeat(TIMER3, //定时器3
LOW_PULSE_VALUE, //一个有效的脉冲周期的低电平时钟周期数。中断可以在这个跳变时产生;
TIMER_COUNT //一个有效的脉冲周期的时钟周期总数。中断可以在这个跳变时产生。
);
}
//软件模拟PWM输出DIO初始化
PRIVATE void vPWMDIOsInit (void)
{
vAHI_DioSetDirection(0, PWM_LED);
vAHI_DioSetPullup(PWM_LED, 0);
}
输出演示:
2、定时器模式
相关宏定义
#define TIMER0 E_AHI_TIMER_0
#define TIMER1 E_AHI_TIMER_1
#define TIMER2 E_AHI_TIMER_2
#define TIMER3 E_AHI_TIMER_3
#define TIMER4 E_AHI_TIMER_4
#define TIMER_PRESCALE 3 /* 定时器预分值,TIMER_FREQUENCY = 16 / (2 ^ TIMER_PRESCALE)*/
#define TIMER_FREQUENCY 16 / (pow(2, TIMER_PRESCALE)) /* 定时器时钟 2MHz */
#define TIMER_TIMING 1000 /* 定时器定时时间 time = 1000us ,时间单位us */
#define TIMER_COUNT TIMER_FREQUENCY * TIMER_TIMING /* 定时器一个有效的脉冲周期的时钟周期数, 即定时器初值*/
#define LOW_PULSE 50 /* 低电平占比(0 - 100),为一个有效的脉冲周期内低电平所占的时钟周期数*/
#define LOW_PULSE_VALUE TIMER_COUNT * (LOW_PULSE / 100.0) /* 低电平时钟数值*/
#define PWM_FREQUENCY 1 / (TIMER_TIMING / 1000000.0) /* PWM频率,单位Hz*/
#define PWM_LED (1 << 3) //软件模拟PWM输出DIO
#define LED_ON(Led) vAHI_DioSetOutput(0, Led)
#define LED_OFF(Led) vAHI_DioSetOutput(Led, 0)
定时器初始化代码
PRIVATE void vTimerInit(void)
{
/*定时器0输出PWM*/
//vAHI_TimerSetLocation(TIMER0, TRUE, FALSE); //将定时器 0 信号从 DIO8-10 传输到 DIO2-4
vAHI_TimerEnable(TIMER3, //定时器3
TIMER_PRESCALE, //8分频,定时器时钟为2MHz
TRUE, //开启上升输出(从低到高)中断
TRUE, //开启定时器周期结束时(从高到低)中断
FALSE); //关闭DIO上定时器信号的输出
vAHI_Timer3RegisterCallback(vCbTimer3); //注册中断回调
/**
* 定时器初值计算方法:
* 外设时钟 fsys = 16MHz
* 定时器时钟根据vAHI_TimerEnable()函数的u8Prescale分频系数算出
* Tsys = 16 / (2 ^ u8Prescale)
* 假设u8Prescale = 3, Tsys = 16 / (2 ^ 3) = 16 / 8 = 2MHz
* 假如要定时 time = 1000us
* 定时器一个有效的脉冲周期u16Lo = time * Tsys = 1000 * 2 = 2000
* 所以vAHI_TimerStartRepeat()函数的 u16Lo = 2000 (一个有效的脉冲周期的时钟周期数)
* 假设开启了vAHI_TimerEnable()函数的定时器周期结束中断bIntPeriodEnable = TRUE,则产生一次中断的时间为 1000us
* vAHI_TimerStartRepeat()函数的 u16Hi 为一个有效的脉冲周期内低电平所占的时钟周期数
* 当 u16Hi = 1000时,即在一个1000us的脉冲周期内,低电平占500us
*/
vAHI_TimerStartRepeat(TIMER3, //定时器3
LOW_PULSE_VALUE, //一个有效的脉冲周期的低电平时钟周期数。中断可以在这个跳变时产生;
TIMER_COUNT //一个有效的脉冲周期的时钟周期总数。中断可以在这个跳变时产生。
);
}
定时器中断回调函数
PRIVATE void vCbTimer3 (uint32 u32Device, uint32 u32ItemBitmap)
{
if(E_AHI_DEVICE_TIMER3 == u32Device){ //定时器3中断
//vPrintf("u32Device = %s \n", "E_AHI_DEVICE_TIMER3");
if(E_AHI_TIMER_RISE_MASK == u32ItemBitmap){ //上升输出(从低到高)中断
if(++count_ris > 499){ //0.5s
count_ris = 0;
vPutStrs((uint8*)"Interrupt = E_AHI_TIMER_RISE_MASK, Count Time = 0.5 second \n");
}
}
if(E_AHI_TIMER_PERIOD_MASK == u32ItemBitmap){ //定时器周期结束时(从高到低)中断
if(++count_sec > 999){ //1s
count_sec = 0;
vPutStrs((uint8*)"Interrupt = E_AHI_TIMER_PERIOD_MASK, Count Time = 1 second \n");
}
}
if((E_AHI_TIMER_RISE_MASK | E_AHI_TIMER_PERIOD_MASK) == u32ItemBitmap){
//vPrintf("Interrupt = %s \n", "E_AHI_TIMER_RISE_MASK & E_AHI_TIMER_PERIOD_MASK");
}
}
}
完整代码
PRIVATE void vPutChar(unsigned char c);
PRIVATE void vPutStrs(unsigned char *str);
PRIVATE void vUartInit(void);
PRIVATE void vCbTimer3 (uint32 u32Device, uint32 u32ItemBitmap);
PRIVATE void vTimerInit(void);
uint32 count_sec = 0;
uint32 count_ris = 0;
PUBLIC void AppColdStart (void)
{
vAHI_WatchdogStop();
(void)u32AHI_Init();
count_sec = 0;
count_ris = 0;
vUartInit();
vTimerInit();
while (1) {
vAHI_CpuDoze(); /* Doze */
}
}
PUBLIC void AppWarmStart (void)
{
AppColdStart();
}
PRIVATE void vCbTimer3 (uint32 u32Device, uint32 u32ItemBitmap)
{
if(E_AHI_DEVICE_TIMER3 == u32Device){ //定时器3中断
//vPutStrs("u32Device = E_AHI_DEVICE_TIMER3 \n");
if(E_AHI_TIMER_RISE_MASK == u32ItemBitmap){ //上升输出(从低到高)中断
if(++count_ris > 499){ //0.5s
count_ris = 0;
vPutStrs((uint8*)"Interrupt = E_AHI_TIMER_RISE_MASK, Count Time = 0.5 second \n");
}
}
if(E_AHI_TIMER_PERIOD_MASK == u32ItemBitmap){ //定时器周期结束时(从高到低)中断
if(++count_sec > 999){ //1s
count_sec = 0;
vPutStrs((uint8*)"Interrupt = E_AHI_TIMER_PERIOD_MASK, Count Time = 1 second \n");
}
}
if((E_AHI_TIMER_RISE_MASK | E_AHI_TIMER_PERIOD_MASK) == u32ItemBitmap){
//vPutStrs("Interrupt = E_AHI_TIMER_RISE_MASK & E_AHI_TIMER_PERIOD_MASK \n");
}
}
}
PRIVATE void vTimerInit(void)
{
/*定时器0输出PWM*/
//vAHI_TimerSetLocation(TIMER0, TRUE, FALSE); //将定时器 0 信号从 DIO8-10 传输到 DIO2-4
vAHI_TimerEnable(TIMER3, //定时器3
TIMER_PRESCALE, //8分频,定时器时钟为2MHz
TRUE, //开启上升输出(从低到高)中断
TRUE, //开启定时器周期结束时(从高到低)中断
FALSE); //关闭DIO上定时器信号的输出
vAHI_Timer3RegisterCallback(vCbTimer3); //注册中断回调
/**
* 定时器初值计算方法:
* 外设时钟 fsys = 16MHz
* 定时器时钟根据vAHI_TimerEnable()函数的u8Prescale分频系数算出
* Tsys = 16 / (2 ^ u8Prescale)
* 假设u8Prescale = 3, Tsys = 16 / (2 ^ 3) = 16 / 8 = 2MHz
* 假如要定时 time = 1000us
* 定时器一个有效的脉冲周期u16Lo = time * Tsys = 1000 * 2 = 2000
* 所以vAHI_TimerStartRepeat()函数的 u16Lo = 2000 (一个有效的脉冲周期的时钟周期数)
* 假设开启了vAHI_TimerEnable()函数的定时器周期结束中断bIntPeriodEnable = TRUE,则产生一次中断的时间为 1000us
* vAHI_TimerStartRepeat()函数的 u16Hi 为一个有效的脉冲周期内低电平所占的时钟周期数
* 当 u16Hi = 1000时,即在一个1000us的脉冲周期内,低电平占500us
*/
vAHI_TimerStartRepeat(TIMER3, //定时器3
LOW_PULSE_VALUE, //一个有效的脉冲周期的低电平时钟周期数。中断可以在这个跳变时产生;
TIMER_COUNT //一个有效的脉冲周期的时钟周期总数。中断可以在这个跳变时产生。
);
}
/*发送单个字符*/
PRIVATE void vPutChar(unsigned char c)
{
while (!(u8AHI_UartReadLineStatus(UART) & E_AHI_UART_LS_THRE)); /*发送FIFO为空*/
vAHI_UartWriteData(UART, c); /*写入要发送的字符*/
/*
发送移位寄存器为空或者发送FIFO为空,即等待发送完毕
E_AHI_UART_LS_THRE | E_AHI_UART_LS_TEMT = 0x20 | 0x40 = 0x60
*/
while ((u8AHI_UartReadLineStatus(UART) & (E_AHI_UART_LS_THRE | E_AHI_UART_LS_TEMT))
!= (E_AHI_UART_LS_THRE | E_AHI_UART_LS_TEMT));
}
/*发送字符串*/
PRIVATE void vPutStrs(unsigned char *str)
{
while(*str){
vPutChar(*str++);
}
}
/*初始化串口*/
PRIVATE void vUartInit(void)
{
#if (UART == E_AHI_UART_0)
vAHI_UartSetRTSCTS(UART, FALSE); /* UART0 2线模式。Disable use of CTS/RTS */
#endif
vAHI_UartEnable(UART);
//复位指定UART的发送和接收FIFO,并将FIFO触发级别设置为单字节触发
vAHI_UartReset(UART,
TRUE, /* 复位收发FIFO*/
TRUE);
vAHI_UartSetBaudRate(UART, E_AHI_UART_RATE_115200); /* 设置波特率*/
vInitPrintf((void *)vPutChar);
}
输出演示: