PDB是什么意思,英文全称是Programmable Delay Block,即可编程延时模块。从中文字面意思上看,每个字都那么熟悉,但是就是无法理解是什意思,很正常,但是再仔细看下技术文档中对该模块的简要介绍,应该就略知一二了吧。下面是文档中英文介绍的翻译:
“可编程延时模块(PDB)为ADC输入的硬件触发或DAC生成的间隔触发提供可控制的内部或外部触发或可编程的间隔时间,以便能使ADC的转换或者DAC的更新达到精准定时。”
上面的介绍是一整句话,但是已经阐释了PDB的整个功能。别看它这么长,说白了就是为ADC或DAC提供硬件触发的,再具体点就是提供什么触发呢,可以提供来自单片机内部模块的触发、外部的触发或者软件可编程的触发。来自单片机内部的模块的触发可以是CMP、PIT、FTM等等。也就是说PDB可以理解为一个桥梁,它接受不同来源的触发,转而再去触发ADC或DAC模块,相当于ADC、或DAC的管家一样。
要讲清楚PDB的工作原理,真的不是一件简单的事情,如果按照技术文档的流程来看,你会越看越晕。技术文档的流程是先介绍特点、再介绍具体的寄存器最后进行功能描述。虽然符合书写原则,但是对于新手来讲确实如同天书一般。我是如何看的呢,如果我对这个模块不了解,那么我会先看一些关于该模块的简要介绍、特点,再去看章节最后一部分的功能描述(Functional Description)。这些功能描述往往会捎带上寄存器的初始化流程、模块的运行流程等信息,这样我就对这个模块有了大体上的了解,最后再去逐一攻破细节。
说了这么多看技术文档的心得,其实是为我接下来的描述找后路,因为很可能你会看不懂我的描述,呵呵。如果真是这样,那就赶紧去啃技术文档吧。我接下来也只是说说PDB的重点需要描述的地方,告诉大家我对于PDB工作方式的理解。首先看下PDB的模块图解,改图描述了整个模块内部的关系机理。
[attach]52638[/attach]
图中两个关键的部分分别是绿框和红框部分,没有这两部分,PDB的工作无从谈起。
绿框部分是PDB的计数器部分,和大部分涉及到时间的外设模块一样,PDB也有计数器、MOD模寄存器等等。有了计数器,就有了基本的定时功能,也就可以控制触发的时间了。由“PDB Counter”框出来的引线,分别引向了橙色框“控制逻辑”部分、蓝色框“中断延时”部分和黄色框“ADC通道触发”部分。也就是说,PDB计数器分别会向这几部分提供计数支持。
蓝框部分是PDB的中断延时功能,当PDB计数器“PDB Counter”的值等于蓝框中“PDBIDLY”寄存器的值时,PDB会根据TOEx位来判断是否产生PDB中断。有了这个功能,PDB就可以被当做一个普通的周期中断定时器来使用了。
橙色框部分是控制逻辑模块,该模块可以理解为是用来控制PDB寄存器复位的。那么那些事件可以引起“PDB Counter”的复位呢?首先看绿框,当“PDBCNT”寄存器的值等于“PDBMOD”模寄存器的值时就会告诉“Control Logic”模块计数器到达预设上限了。如果“CONT”位为1表示PDB为连续工作模式,则“Control Logic”通知“PDB Counter”复位重新计数,如果果“CONT”位为0表示PDB为单次转换模式,则“Control Logic”等待触发事件的到来,这个触发事件就是红框表示出来的部分。当触发事件发生,则“Control Logic”通知“PDB Counter”复位重新计数。
红框部分就是上面说的触发模块了,触发源由“TRIGSEL”位进行选择,触发源可以是Trigger-In 0~14或“SWTRIG”软件触发中的任意一个。从图中还可以看出,触发源出来的引向不止用来控制PDB的复位,还引向了“DAC interval trigger x”DAC间隔触发器,也就是说,触发源可以触发DAC间隔触发器产生DAC触发,当“DAC Interval Counter x”间隔计数器的值等于“DACINTx”的值的时候,就会输出DAC触发事件。另外触发源还引向了黄框的部分,即ADC。
黄色框是通道n的预触发m模块,什么n呀m啊估计你已经荤菜了。简单理解就是n代表ADC的0或1模块,m为ADCx的A或B组通道。也就是说“Ch0 pre-trigger 0”用来触发ADC0的A组通道,同理“Ch0 pre-trigger 1”用来触发ADC0的B组通道。名字理解了,但是这个黄框内的复杂关系估计还是会让你晕菜,没关系,我们从已知的部分开始看。当触发源控制计数器复位开始计时后,“PDB Counter”的值就一直和“PDBCHnDLYm”的值进行对比,当相等后就会最终输出“Pre-trigger m”事件,这个就是ADC的硬件触发信号。图中的什么Ack、BB等等信号,是Back to Back模式中需要用到的,这个我们会在以后讲到,今天大家能消化这些就不错了。
可能有朋友会问了,触发就触发呗,怎么还搞个预触发,太复杂了。其实仔细想想就不难理解,从上图的模块图中不难看出,触发源只能选择一个,而相对来说ADC0模块有两组通道A和B,如果PDB在收到触发后就就立即触发A和B组是不可能的,因为ADCx的每组通道是不可能同时进行转换的,必定有一个先后顺序,有了先后顺序就必定需要有延时等待,因此就需要了上面讲到的“PDBCHnDLYm”通道延时寄存器,而这个延时计数的过程就是预触发过程,当“PDBCHnDLYm”的值等于“PDB Counter”的值时所导致的触发就是预触发。我们还可以从下面的示例图中看出这个过程:
当PDB接收到了触发源的输入事件后(红色线开始),“PDB Counter”就会开始计数,持续“PDBCHnDLY0”的时间后,也就是黄色线的长度,会产生“Ch n pre-trigger 0”预触发信号,与此同时,对应产生“Ch n trigger”通道n的触发事件。以此类推,当等待“PDBCHnDLYm”的时间后,产生“Ch n pre-trigger m”预触发信号和对应的触发信号。
说了这么多天书,还是来实践一下才是正道。首先我们做点简单的事情,让PDB先运行起来,不触发任何东西,就自己生成定时中断好了。打开例程“LPLD_PdbPeriodicInt”,这个例程实现的功能和PIT的功能类似,我们将在周期产生的中断函数中输出信息。具体先看初始化函数pdb_init()的代码:
1 | pdb_init_struct.PDB_CounterPeriodMs = 1000; //计数器溢出周期1000毫秒 |
2 | pdb_init_struct.PDB_TriggerInputSourceSel = TRIGGER_SOFTWARE; //触发源为软件触发 |
3 | pdb_init_struct.PDB_ContinuousModeEnable = TRUE; //连续工作模式 |
4 | pdb_init_struct.PDB_DelayMs = 200; //中断延时时间200毫秒 |
5 | pdb_init_struct.PDB_IntEnable = TRUE; //使能延时中断 |
6 | pdb_init_struct.PDB_Isr = pdb_isr; //中断函数设置 |
7 | LPLD_PDB_Init(pdb_init_struct); |
8 | LPLD_PDB_EnableIrq(); |
9 | LPLD_PDB_SoftwareTrigger(); |
Line 1:配置PDB计数器的溢出周期为1000毫秒,库函数会根据我们设置的时间,自动计算出MOD寄存器、MULT因子、PRESCALER分频的值。
Line 2:利用成员变量PDB_TriggerInputSourceSel配置PDB的触发源为软件触发,TRIGGER_SOFTWARE。
Line 3:配置PDB的工作模式为连续工作,即图中CONT位的值为1,PDB计数器会在等于MOD的值后立即复位并继续计数。
Line 4:配置中断延时时间为200毫秒,库函数会根据这个时间自动计算出“PDBIDLY”寄存器的值。
Line 5~6:使能PDB中断,配置中断函数。
Line 7:初始化PDB。
Line 8:使能中断请求。
Line 9:软件触发PDB开始工作。这个函数的作用就是让图中红框部分产生一个软件触发信号给“Control Logic”。
配置完PDB后,PDB的延时中断功能就会立即开始工作,不过程序会运行以下这句代码:
1 | printf("Waiting for 200ms, and then:\r\n"); |
等待200ms后,第一次中断会发生,再等待1000ms后,第二次中断会发生。以后每个1000ms会发送一次中断。也就是说,初始化完成后,程序首先会延时200ms,才会触发第一次中断。这就是初始化时设置PDB_DelayMs为200ms的效果了。如果你不明白为什么,可以回头去看看第一幅PDB模块图。
与前面几个例程不同,这是一个综合例程,我们要配合使用ADC+PDB+PIT这个3个模块来实现自动定时采集功能。打开例程“LPLD_PdbAnalogSample”,看main()函数中的初始化代码,首先初始化ADC的相关设置:
1 | adc0_init_struct.ADC_Adcx = ADC0; //选择ADC0 |
2 | adc0_init_struct.ADC_BitMode = SE_12BIT; //配置转换精度 |
3 | adc0_init_struct.ADC_CalEnable = TRUE; //使能初始化自动校准 |
4 | adc0_init_struct.ADC_HwTrgCfg = HW_TRGA; //配置为硬件触发转换 |
5 | adc0_init_struct.ADC_Isr = adc0_isr; //设置ADC中断函数 |
6 | LPLD_ADC_Init(adc0_init_struct); |
7 | LPLD_ADC_Chn_Enable(ADC0, DAD1); |
8 | LPLD_ADC_EnableConversion(ADC0, DAD1, 0, TRUE); |
9 | LPLD_ADC_EnableIrq(adc0_init_struct); |
Line 4:前面几行没有要说的,主要是这里配置成员变量ADC_HwTrgCfg使ADC0为硬件触发转换模式。
Line 5:配置ADC中断函数,由于我们没有使用DMA功能,因此需要在中断里来获取转换结果。
Line 8:调用LPLD_ADC_EnableConversion()函数使能转换,该函数的前两个参数分别设置模块号和通道号,第3个参数设置转换通道为A组,第4个参数为使能转换结束中断。
Line 9:使能中断请求。
接下来初始化PDB的相关设置:
1 | pdb_init_struct.PDB_CounterPeriodMs = 1000; //PDB计数器周期设置 |
2 | pdb_init_struct.PDB_LoadModeSel = LOADMODE_0; //加载模式设置 |
3 | pdb_init_struct.PDB_ContinuousModeEnable = FALSE; //禁用连续工作模式 |
4 | pdb_init_struct.PDB_TriggerInputSourceSel = TRIGGER_PIT0; //配置触发源为PIT0 |
5 | LPLD_PDB_Init(pdb_init_struct); |
6 | LPLD_PDB_AdcTriggerCfg(ADC0, PRETRIG_EN_A, 0); |
Line 2:这是第1个例程没有配置过的参数,配置PDB_LoadModeSel加载模式,这里选择模式LOADMODE_0。加载模式指的是PDB寄存器中如CNT、MOD、DLY寄存器的加载模式,具体描述请参见PDB模块在线函数手册。(点击此处)
Line 3:禁用连续工作模式。
Line 4:配置PDB输入触发源为TRIGGER_PIT0,即PIT0模块。
Line 6:PDB对ADC的触发配置,调用函数LPLD_PDB_AdcTriggerCfg(),配置预触发ADC0模块,预触发A通道使能,预触发延时为0。
最后配置PDB的触发源,PIT模块:
1 | pit0_init_struct.PIT_Pitx = PIT0; //选择PIT0 |
2 | pit0_init_struct.PIT_PeriodMs = 500; //PIT0计数周期500ms |
3 | LPLD_PIT_Init(pit0_init_struct); |
Line 2:配置PIT0技术周期为500毫秒,即每过500毫秒触发一次PDB。那么它和PDB的1000ms计数周期什么关系呢,这里的意思是没500ms触发一次PDB开始工作,这是PDB开始计数,其实PDB根本计不到1000ms就会被下一次PIT0触发。
Line 3:直接初始化PIT0,不用设置中断函数,也不用使能中断请求,因为PIT只需要产生触发信号,不用产生中断。
全部初始化完毕,整个工程才真正开始运作,首先PIT会触发PDB开始工作,PDB收到触发输入后经过0秒延迟预触发ADC0模块的A组转换通道,A组通道被触发后开始AD转换工作,转换完成后触发中断运行函数adc0_isr(),看其代码:
1 | void adc0_isr() |
2 | { |
3 | int16 result; |
4 | uint8 ab = LPLD_ADC_GetSC1nCOCO(ADC0); |
5 | result = LPLD_ADC_GetResult(ADC0, ab); |
6 | printf("ADC0_R[%d]=%d\r\n", ab, result); |
7 | } |
Line 4:首先调用LPLD_ADC_GetSC1nCOCO()函数查看到底是ADC0的A组还是B组产生的中断,因为ADCx的A、B组通道都进入同一个中断,因此必须用此函数判断是哪组产生的中断。
Line 5:调用LPLD_ADC_GetResult()函数获取ADC0模块A组通道的转换结果,该函数和ADC的LPLD_ADC_Get()函数都是获取转换结果,但是前者不会等待转换结束,且必须指定A、B组通道,后者会等待转换结束,且只获取A组结果(因为只有A组能软件触发转换)。
本节课只介绍了PDB的定时中断和ADC触发例程,其实PDB还可以触发DAC模块,大家可以在了解OSKinetis V3库函数的基础上自行学习使用。
而且关于触发ADC的转换,更加效率的方式是配合DMA的使用。这样的话,就不需要ADC产生中断来获取转换结果了,可以直接通过DMA的ADC请求源来不依靠CPU获取转换结果。具体例程可以参考“LPLD_DmaPdbAnalogSample”。
更加牛叉的方法是利用ADC的乒乓模式配合PDB的back to back功能,来完成4个ADC通道的自动连续采集,实际案例请参考“LPLD_DmaPdbADCx4”。
欢迎光临 智能车制作 (http://dns.znczz.com/) | Powered by Discuz! X3.2 |