本帖最后由 sujingliang 于 2024-12-30 22:11 编辑
可编程延迟单元(PDU)是极海G32A1465开发板上一个很独特的外设,也许是见识有限,没有在其他MCU上发现有类似的单元。
在延时方面,PDU无疑是SysTick、定时器之外的又一个新的选择。
在毫秒级延时上,SysTick、定时器在准确性上都有不错的表现,SysTick有配置简便易于理解的优势,定时器不占优势。
在微秒级延时上,SysTick不被推荐,定时器是较好的选择。
至于PDU,之前没有接触过,不过从例程上看应该可以支持微秒级延时,下面就简单分析一下。
例程位于:G32A1xxx_SDK_V1.1\Examples\G32A1465\PDU\PDU_PeriodicInterrupt
一、手册上关于PDU的描述
PDU被归为定时器
提供可控制的延迟,从内部或外部触发器,或可编程间隔刻度,到 ADC 的硬件
触发输入。PDU 可以选择性地提供脉冲输出,用作 COMP 块中的采样窗口。
触发
⚫ 最多 2 个触发输入源和一个软件触发源
⚫ 触发器输出可以独立使能或禁止
⚫ 每个预触发输出一个 16 位延迟寄存器
⚫ 可选的旁路延迟寄存器的预触发输出
⚫ 每个预触发器一个通道标志和一个序列错误标志
ADC
⚫ 8 个可配置的 PDU 通道用于 ADC 硬件触发器
⚫ 1 个 PDU 通道对应 1 个 ADC
⚫ 1 个触发输出用于 ADC 硬件触发,最多 8 个预触发输出用于每个 PDU
通道的 ADC 触发选择
操作在一次或连续模式
可选背靠背模式操作,可实现 ADC 转换完成以触发下一个 PDU 通道
脉冲输出
⚫ 8 个脉冲输出
⚫ 脉冲输出可以独立使能或禁止
⚫ 可编程脉冲宽度
DMA 支持
中断
⚫ 1 个可编程延迟中断
⚫ 1 个序列错误中断
二、从例程入手,稍作修改
只看手册云里雾里,还是从例程上分析吧
1、PDU配置参数g_pduTimerConfig
const PDU_TIMER_CONFIG_T g_pduTimerConfig =
{
.loadValMode = PDU_LOAD_VAL_IMMEDIATELY,
.clkPscDiv = PDU_CLK_PSCDIV1,
.clkPscMultFactor = PDU_CLK_PSCMULT_FACTOR_AS_1,
.triggerInput = PDU_SOFTWARE_TRIGGER,
.intEn = true,
.seqErrIntEn = false,
.continuousModeEn = true,
.dmaEn = false,
.instanceBackToBackEn = false,
};
g_pduTimerConfig 用于PDU_Init函数的参数,初始化PDU:
.loadValMode = PDU_LOAD_VAL_IMMEDIATELY,
对应LDMODSEL寄存器
PDU_LOAD_VAL_IMMEDIATELY为0,当控制寄存器LDSUS设置为1时,内部寄存器立即从它们的缓冲区中加载值。
typedef enum
{
/* Loaded immediately after load operation. */
PDU_LOAD_VAL_IMMEDIATELY = 0U,
/* Loaded when counter hits the modulo after load operation. */
PDU_LOAD_VAL_AT_MODULO_COUNTER = 1U,
/* Loaded when detecting an input trigger after load operation. */
PDU_LOAD_VAL_AT_NEXT_TRIGGER = 2U,
/* Loaded when counter hits the modulo or detecting an input trigger after load operation. */
PDU_LOAD_VAL_AT_MODULO_COUNTER_OR_NEXT_TRIGGER = 3U
} PDU_LOAD_VAL_MODE_T;
.clkPscDiv = PDU_CLK_PSCDIV1,
原例程是PDU_CLK_PSCDIV128,这个参数对应PSCDIVCFG寄存器
typedef enum
{
PDU_CLK_PSCDIV1 = 0U, /* ( Prescaler multiplication factor ) x ( Counter divided by 1 ) */
PDU_CLK_PSCDIV2 = 1U, /* ( Prescaler multiplication factor ) x ( Counter divided by 2 ) */
PDU_CLK_PSCDIV4 = 2U, /* ( Prescaler multiplication factor ) x ( Counter divided by 4 ) */
PDU_CLK_PSCDIV8 = 3U, /* ( Prescaler multiplication factor ) x ( Counter divided by 8 ) */
PDU_CLK_PSCDIV16 = 4U, /* ( Prescaler multiplication factor ) x ( Counter divided by 16 ) */
PDU_CLK_PSCDIV32 = 5U, /* ( Prescaler multiplication factor ) x ( Counter divided by 32 ) */
PDU_CLK_PSCDIV64 = 6U, /* ( Prescaler multiplication factor ) x ( Counter divided by 64 ) */
PDU_CLK_PSCDIV128 = 7U /* ( Prescaler multiplication factor ) x ( Counter divided by 128 ) */
} PDU_CLK_PSCDIV_T;
设置为PDU_CLK_PSCDIV1,不分频。
.clkPscMultFactor = PDU_CLK_PSCMULT_FACTOR_AS_1,
原例程是PDU_CLK_PSCMULT_FACTOR_AS_10
这个参数对应寄存器MULPSCCFG配置预分频器乘法系数
typedef enum
{
PDU_CLK_PSCMULT_FACTOR_AS_1 = 0U, /* Multiplication factor is 1. */
PDU_CLK_PSCMULT_FACTOR_AS_10 = 1U, /* Multiplication factor is 10. */
PDU_CLK_PSCMULT_FACTOR_AS_20 = 2U, /* Multiplication factor is 20. */
PDU_CLK_PSCMULT_FACTOR_AS_40 = 3U /* Multiplication factor is 40. */
} PDU_CLK_PSCMULT_FACTOR_T;
.triggerInput = PDU_SOFTWARE_TRIGGER,
对应SWTRGSEL寄存器
软件触发
.continuousModeEn = true,
对应CMODEN寄存器
连续模式
2、main初始化
PDU_Init(PDU0_INSTANCE, &g_pduTimerConfig);
/* Calculate the required value of the PDU timer counter for the specified timeout */
if (!PDU_CalculateIntTimerValue(&g_pduTimerConfig, PDU_TIMEOUT_US, &pduIntTimerValue))
{
goto end;
}
/* Set the period of the counter */
PDU_ConfigPeriodCountValue(PDU0_INSTANCE, pduIntTimerValue);
/* Set the delay value to schedule the PDU interrupt */
PDU_ConfigTimerIntDelayValue(PDU0_INSTANCE, pduIntTimerValue);
/* Enable PDU */
PDU_Enable(PDU0_INSTANCE);
/* Executes the command of loading values */
PDU_LoadValuesCmd(PDU0_INSTANCE);
/* Software trigger the pdu counter */
PDU_EnableSoftTrigger(PDU0_INSTANCE);
PDU_Init(PDU0_INSTANCE, &g_pduTimerConfig);
初始化PDU的实例,配置分频、中断等参数。
if (!PDU_CalculateIntTimerValue(&g_pduTimerConfig, PDU_TIMEOUT_US, &pduIntTimerValue))
这行代码调用PDU_CalculateIntTimerValue函数来计算在给定的周期时间
PDU_ConfigPeriodCountValue(PDU0_INSTANCE, pduIntTimerValue);
使用计算得到的定时器值pduIntTimerValue来配置PDU实例的周期计数值。当PDU的计数器达到这个值时,会触发一个周期事件。
此函数将 CNTPRD=pduIntTimerValue
PDU_ConfigTimerIntDelayValue(PDU0_INSTANCE, pduIntTimerValue);
使用pduIntTimerValue来配置PDU实例的中断延迟值。
此函数将 SINTDLY=pduIntTimerValue
PDU_Enable(PDU0_INSTANCE);
启用PDU实例,使其开始运行。
此函数将 PDUEN=1
PDU_LoadValuesCmd(PDU0_INSTANCE);
执行加载值命令,将之前配置的所有参数(如周期计数值和中断延迟值)加载到PDU的硬件寄存器中。
此函数将 LDSUS=1
PDU_EnableSoftTrigger(PDU0_INSTANCE);
软件触发PDU计数器。
此函数将 SWTRGSEL=1
3、PDU_CalculateIntTimerValue函数
main中有如下调用
PDU_CalculateIntTimerValue(&g_pduTimerConfig, PDU_TIMEOUT_US, &pduIntTimerValue)
PDU_CalculateIntTimerValue用于计算周期/中断数值,计算公式:
tempIntTimerValue = (pduClkFreq * us) / (clkPscDiv * clkPscMultFactor);
这里us为PDU_TIMEOUT_US,可以参数化设置,设PDU_TIMEOUT_US=1000,即1ms
已知:pduClkFreq =48mhz,clkPscDiv =1(不分频),clkPscMultFactor=1(乘法因子为1)
tempIntTimerValue=48000000*1000/(1*1)=48000
根据寄存器SINTDLY说明
tempIntTimerValue应满足:0<tempIntTimerValue<0xFFFF
4、PDU0_IRQHandler中断处理函数
void PDU0_IRQHandler(void)
{
if (PDU_ReadTimerIntFlag(PDU0_INSTANCE) != false)
{
/* Clear PDU Interrupt flag */
PDU_ClearTimerIntFlag(PDU0_INSTANCE);
/* Toggle green LED */
//LED_Toggle(LED_GREEN);
pdu_count++;
}
}
每1ms进入中断一次,pdu_count加1。
5、while(1)
while (1)
{
if(pdu_count>=500)
{
pdu_CNTCUR=PDU_ReadTimerValue(PDU0_INSTANCE);
printf("pdu_CNTCUR:%d\r\n",pdu_CNTCUR);
printf("PDU timing 500ms.\r\n");
pdu_count=0;
}
}
判断pdu_count,大于等于500(500ms),打印信息:
CNTCUR(计算器当前值),500ms信息,清pdu_count为0。
6、PDU_TIMEOUT_US
#define PDU_TIMEOUT_US (1000UL)
可以通过改变PDU_TIMEOUT_US数值,改变进入中断的时间,从字面上看可以设置到1us。
|