MCU微课堂 | CKS32K148 PDB

PDB(可编程延迟模块)提供从触发源到ADC的硬件触发器输入的可控延迟,触发源来自内部、外部触发器或可编程间隔时钟。PDB可以选择性的提供脉冲输出,脉冲输出可用作CMP(比较器)中的采样窗口。

CKS32K148有两个PDB,每个PDB模块有4个通道、一次脉冲输出和1个触发器,每个通道有8个预触发器。因此,一个PDB模块一次可以触发ADC多达32个输入通道。

结构与框图

CKS32K148系列MCU的PDB框图如下所示,大体可划分成六个部分。

1.png

图1 AIPS结果框图

① 触发源选择部分:PDB的触发源选择通过PDB_SC寄存器中的TRGSEL位确定,其可以是软件触发-通过向PDB_SC寄存器的SWTRIG位写入1实现;也可以是外部触发源-如外部触发引脚Trigger_In0。

② 计数器部分:PDB的时钟源经过PDB_SC寄存器中的PRESCALER位选择的分配系数进行分频,通过PDB_SC寄存器中的MULT位选择倍频系数,之后计数器开始工作。计数器当前计数值可以通过计数器寄存器PDB_CNT读取。计数器的周期通过PDB_MOD模数寄存器指定,当计数器达到该值时,将被重置为零。

计数器的控制逻辑通过PDB_SC寄存器中的CONT位实现:当该位无效时,计数器达到周期值时被重置为零,直至下一次触发出现后重新开始计数;当该位有效时,计数器达到周期值时自动重新开始。

③ 预触发器输出部分:PDB通道n(n = 0 ~ 3)预触发器输出0~m(m最大值为7),每个预触发输出连接ADC硬件触发选择和硬件触发输入,预触发可以使用PDB通道预触发使能(CHn1C1[EN[m]])启用或禁止。

为方便叙述,定义触发器输入事件为:在所选触发输入源上检测到上升沿,或者选择软件触发器并将软件触发器位(SC[SWTRIG])写入1。预触发源输出对应三种情况:

a)当触发器输入事件发生时,在2个外围时钟周期后置位预触发器m。这种情况下,需要清零CHnC1[TOS[m]]。

b)使用通道延迟寄存器CHnDLYm指定延迟值,当计数器计数值达到该值时,经过两个外围时钟周期后置位预触发器m。这种情况下,需要置位CHnC1[TOS[m]]。

c)PDB配置成背靠背操作,背靠背操作使得ADC转换完成触发下一个PDB通道预触发和触发输出。换句话说,预触发m-1触发ADC转换完成之后产生的Ack信号会使预触发输出m置位两个外围时钟周期。这种情况下,需要置位CHnC1[BB[m]]。

④ 触发输出部分:预触发输出用于在实际触发器发生之前对ADC块进行预处理。当ADC接收到触发器的上升沿时,ADC将根据预触发器决定的先决条件开始转换,PDB通道n的预触发和触发输出如下图所示。

2.png

图2 预触发输出和触发输出

如果PDB通道n的预触发器被置位时置位一个新的预触发器m,那么会产生PDB通道序列错误标志(CHnS[ERR[m]]置位)。如果使能PDB序列错误中断,则产生序列错误中断。序列错误通常是由于延迟m设置的太短,并且预触发m在先前触发的ADC转换之前置位。例如预触发m-1置位并触发ADC转换这一过程还未结束时,此时预触发m置位则会产生序列错误。

⑤ 脉冲输出部分:PDB可以产生可配置宽度的脉冲输出。当PDB计数器达到PonDLY[DLY1]中设置的值时,脉冲输出拉高;当PDB计数器达到PonDLY[DLY2]中设置的值时,脉冲输出拉低。PonDLY[DLY2]可以设置为大于或者小于PonDLY[DLY1]中的值。

⑥ 中断部分:中断延迟寄存器PDB_IDLY指定PDB中断的延迟值,可以用来在PDB周期的某个点安排一个独立的中断。如果使能中断(PDB_SC[PDBIE]置位),当计数器等于IDLY时,将产生一个PDB中断。

PDB延迟触发ADC多通道实验

CKS32K148 PDB最常用的功能就是为ADC提供硬件触发源,我们这里展示PDB延迟触发ADC功能。即前文介绍过预触发输出的b)情况-使用通道延迟寄存器CHnDLYm指定延迟值,当计数器计数值达到该值时,经过两个外围时钟周期后置位预触发器m。

前面介绍过,每个PDB模块有4个通道、1个触发器和一次脉冲输出,每个通道有8个预触发输出,这里我们只用到1个通道、1个触发器和8个预触发输出,用来触发ADC的8个输入通道。为了避免出现PDB序列错误,需要确保预触发器之间有足够的延迟。

编程要点如下:

1. 使能相关外设时钟,如ADC、PDB等,并配置用到的引脚

status = CLOCK_DRV_Init(&clockMan1_InitConfig0);DEV_ASSERT(status == STATUS_SUCCESS);status = PINS_DRV_Init(NUM_OF_CONFIGURED_PINS0, g_pin_mux_InitConfigArr0);DEV_ASSERT(status == STATUS_SUCCESS);

2. 配置ADC结构体参数、输入通道。此处仅展示前2个通道配置函数

ADC_DRV_ConfigConverter(INST_ADC_0, &ADC_0_ConvConfig0);

PDB_DRV_ConfigAdcPreTrigger(INST_PDB_0, 0U, &pdb_1_adcTrigConfig0);

PDB_DRV_ConfigAdcPreTrigger(INST_PDB_0, 0U, &pdb_1_adcTrigConfig1);

/*** ADC初始化结构体***/

const adc_converter_config_t ADC_0_ConvConfig0 = {

  .clockDivide = ADC_CLK_DIVIDE_4,

  .sampleTime = 255U,

  .resolution = ADC_RESOLUTION_12BIT,

  .inputClock = ADC_CLK_ALT_1,

  .trigger = ADC_TRIGGER_HARDWARE,

  .pretriggerSel = ADC_PRETRIGGER_SEL_PDB,

  .triggerSel = ADC_TRIGGER_SEL_PDB,

  .dmaEnable = false,

  .voltageRef = ADC_VOLTAGEREF_VREF,

  .continuousConvEnable = false,

  .supplyMonitoringEnable = false

};

关键在于配置ADC为硬件触发方式,预触发源和触发源均为PDB,禁止连续模式。配置ADC8个输入通道:指定外部/内部输入通道,每个通道均使能转换完成中断。

3. PDB计数器配置和使能

PDB_DRV_Init(INST_PDB_0, &pdb_1_timerConfig0);
PDB_DRV_Enable(INST_PDB_0);
/*** PDB计数器初始化结构体***/
const pdb_timer_config_t pdb_1_timerConfig0 = 
{    
    .loadValueMode = PDB_LOAD_VAL_IMMEDIATELY,    
    .seqErrIntEnable = false,    
    .clkPreDiv = PDB_CLK_PREDIV_BY_128,    
    .clkPreMultFactor = PDB_CLK_PREMULT_FACT_AS_10,    
    .triggerInput = PDB_SOFTWARE_TRIGGER,    
    .continuousModeEnable = true,    
    .dmaEnable = false,    
    .intEnable = false,    
    .instanceBackToBackEnable = false,
};

参数loadValueMode:用于选择加载模式,控制PDB操作时间的寄存器(如模数寄存器、中断延迟寄存器等)可能需要同时变得有效。这些寄存器被写入的值会先更新到它们的缓冲区,因此加载模式就用来选择达到何种条件时缓冲区的值更新到内部寄存器的情况。这里我们选择默认值,即立即更新缓冲区的加载值。

参数seqErrIntEnable:用于设置PDB序列错误中断的使能与否。这里我们关闭使能。

参数clkPreDiv:用于选择预分频系数。这里我们设置成128分频。

参数clkPreMultFactor:用于选择倍频系数。这里我们设置成10倍频。

参数triggerInput:用于选择触发输入源。这里选择软件触发方式。

参数continuousModeEnable:用于选择是否使能连续模式。配置使能连续模式,所以当计数器达到周期值时,自动从零开始重新计数。

参数dmaEnable:用于选择是否使能DMA。如果使能DMA,当PDB计数器达到中断延迟寄存器PDB_IDLY指定的延迟值时,PDB中断标志置位,PDB请求一个DMA传输。这里我们禁止DMA。

参数intEnable:用于选择是否使能计数器中断。只有当DMA失能时才会产生计数器延迟中断。这里我们禁止计数器中断。

参数instanceBackToBackEnable:用于选择是否使能背靠背操作,对应前面介绍的预触发输出情况c)。这里我们禁止背靠背操作。

4. PDB计数器周期值配置

calculateIntValue(&pdb_1_timerConfig0, PDLY_TIMEOUT, &delayValue0);
PDB_DRV_SetTimerModulusValue(INST_PDB_0, (uint32_t) delayValue0);

PDLY_TIMEOUT为用户设置的周期值,我们设为1s。calculateIntValue函数可将用户设置的计数器周期根据计数器的时钟频率转换成对应的十六进制数,之后将该十六进制数写入PDB_MOD寄存器中。

5. PDB预触发输出配置(此处仅展示通道0的前两个预触发输出函数配置)

PDB_DRV_ConfigAdcPreTrigger(INST_PDB_0, 0U, &pdb_1_adcTrigConfig0);
PDB_DRV_ConfigAdcPreTrigger(INST_PDB_0, 0U, &pdb_1_adcTrigConfig1);
/*** 预触发输出配置结构体***/
const pdb_adc_pretrigger_config_t pdb_1_adcTrigConfig0 = 
{    
    .adcPreTriggerIdx = 0U,    
    .preTriggerEnable = true,    
    .preTriggerOutputEnable = true,    
    .preTriggerBackToBackEnable = false
};

上述参数分别为:预触发输出序号(0~7),是否使能预触发,是否使能预触发输出以及是否使能背靠背操作。其余预触发输出除输出序号不同外其他配置均相同。

6. 设置预触发延迟值

PDB_DRV_SetAdcPreTriggerDelayValue(INST_PDB_0, 0UL, 0UL,(uint32_t) delayValue0 / 9);
PDB_DRV_SetAdcPreTriggerDelayValue(INST_PDB_0, 0UL, 1UL,(uint32_t) delayValue0 / 8);
PDB_DRV_SetAdcPreTriggerDelayValue(INST_PDB_0, 0UL, 2UL,(uint32_t) delayValue0 / 7);
PDB_DRV_SetAdcPreTriggerDelayValue(INST_PDB_0, 0UL, 3UL,(uint32_t) delayValue0 / 6);
PDB_DRV_SetAdcPreTriggerDelayValue(INST_PDB_0, 0UL, 4UL,(uint32_t) delayValue0 / 5);
PDB_DRV_SetAdcPreTriggerDelayValue(INST_PDB_0, 0UL, 5UL,(uint32_t) delayValue0 / 4);
PDB_DRV_SetAdcPreTriggerDelayValue(INST_PDB_0, 0UL, 6UL,(uint32_t) delayValue0 / 3);
PDB_DRV_SetAdcPreTriggerDelayValue(INST_PDB_0, 0UL, 7UL,(uint32_t) delayValue0 / 2);

前面我们设置PDB周期为1s,因此预触发输出1和预触发输出0之间的延迟为1 * (1/8 - 1/9) = 13.88ms。同理,预触发2和预触发1之间的延迟为17.925ms,预触发3和预触发2之间的延迟为11.9ms,预触发4和预触发3之间的延迟为16.65ms,预触发5和预触发4之间的延迟为25ms,预触发6和预触发5之间的延迟为41.65ms,预触发7和预触发6之间的延迟为83.3ms。

7. 执行加载命令,使能软件触发

PDB_DRV_LoadValuesCmd(INST_PDB_0);
PDB_DRV_SoftTriggerCmd(INST_PDB_0);

执行加载命令后,先前设置好的相关寄存器值(PDB_MOD、PDB_CHnDLYm)立即从缓冲区更新到内部寄存器。软件触发PDB后,计数器开始工作。

8. 使能ADC中断

INT_SYS_InstallHandler(ADC0_IRQn, &ADC0_IRQHandler, (isr_t*) 0);
INT_SYS_EnableIRQ(ADC0_IRQn);

9. 编写中断服务函数:在中断中获取ADC结果,同时翻转IO电平

void ADC0_IRQHandler(void)

{

    ADC_DRV_GetChanResult(INST_ADC_0, 0UL, (uint16_t *)&adcRawValue0[0]);

    ADC_DRV_GetChanResult(INST_ADC_0, 1UL, (uint16_t *)&adcRawValue0[1]);

    ADC_DRV_GetChanResult(INST_ADC_0, 2UL, (uint16_t *)&adcRawValue0[2]);

    ADC_DRV_GetChanResult(INST_ADC_0, 3UL, (uint16_t *)&adcRawValue0[3]);

    ADC_DRV_GetChanResult(INST_ADC_0, 4UL, (uint16_t *)&adcRawValue0[4]);

    ADC_DRV_GetChanResult(INST_ADC_0, 5UL, (uint16_t *)&adcRawValue0[5]);

    ADC_DRV_GetChanResult(INST_ADC_0, 6UL, (uint16_t *)&adcRawValue0[6]);

    ADC_DRV_GetChanResult(INST_ADC_0, 7UL, (uint16_t *)&adcRawValue0[7]);


    PINS_DRV_TogglePins(LED_PORT, 1 << LED0);


    adc0ConvDone = true;

}

至此,PDB多通道延迟触发ADC的例程基本讲述完毕。程序编译后烧录至开发板,PDB预触发每置位并触发ADC转换完成后即进入中断翻转IO电平。用户根据抓取到的IO电平波形即可验证延迟值是否与预期一致。此外,如果想要查看ADC转换值,用户可使能串口,通过adc0ConvDone标志位在主函数while循环中打印输出结果,此处不再详细展开。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。