之前在做智能车的时候需要对LC振荡电路进行频率采集(其实如果频率变化率比较大,直接选择去掉整流电路采集电压的变化也可行),研究了一段时间的K60脉冲计数。K60本身的配置和功能就不多说了,就直接切入正题吧。(以下的所有代码都是基于LPLD库实现的,野火或者山外的就自己去论坛找找,有相应的实现代码)
FTM模块是一个多功能定时器模块,主要功能有PWM输出、输入捕捉、输出比较、定时中断、脉冲加减计数、脉冲周期脉宽测量。K60中包含有3个互相独立的FTM模块:FTM0、FTM1、FTM2,其中MK60F系列还含有额外的FTM3模块。FTM模块最常用的功能是实现PWM输出,但是只有FTM1、FTM2模块具备正交解码功能,而正交解码功能正是我们实现脉冲计数采集所需要的。
PS:实际上FTM的输入捕获也是可以实现脉冲计数的,但是一方面考虑到是通过软件中断,本身程序中已经有设置了一个中断进行K60控制,担心会影响主要程序的运行,另一方面,输入捕捉在频率较高的情况下采集精度不高,不适合对脉冲数进行处理。
正交解码听起来挺厉害的,但是实际上实现起来恨简单(反正都是调用库函数),正交解码最常用的是用来采集编码器的正反信号,那个就是采集的脉冲信号,如果我们只考虑编码器的正转的话就完全可以等效为脉冲计数采集,同时及时在较高频率的情况下也能保证精度。
下面就给出具体的代码:
//初始化FTM的正交解码模块,其中通道0为需要采集的脉冲端口,通道1接地
void Qd_Init(void)
{
FTM_InitTypeDef ftm_init_struct;
ftm_init_struct.FTM_Ftmx = FTM2; //只有FTM1和FTM2有正交解码功能
ftm_init_struct.FTM_Mode = FTM_MODE_QD; //正交解码功能
ftm_init_struct.FTM_QdMode = QD_MODE_CNTDIR; //计数和方向解码模式
ftm_init_struct.FTM_ClkDiv = FTM_CLK_DIV64; //128分频
LPLD_FTM_Init(ftm_init_struct); //初始化FTM
}
//获取脉冲数
short int pulseCount = LPLD_FTM_GetCounter(FTM2); //获取计数器中采集到脉冲数
LPLD_FTM_ClearCounter(FTM2); //每次采集之后都要对FTM计数器清零
PS:如果出现采集的数据一直是65535之类的,很有可能是数据类型出错了,尝试一下其他的整数类型
LPTMR就没什么好说的了,在配置的时候选择脉冲累计模式就行了。
下面是代码:
//LPTMR初始化
void LPTMR_Init()
{
LPTMR_InitTypeDef lptmr_init_struct;
lptmr_init_struct.LPTMR_Mode = LPTMR_MODE_PLACC; //脉冲计数模式
lptmr_init_struct.LPTMR_PluseAccInput = LPTMR_ALT2; //选择ALT2通道,PTC5,具体的通道在对应头文件中查看
lptmr_init_struct.LPTMR_Isr = NULL;
LPLD_LPTMR_Init(lptmr_init_struct); //初始化LPTMR
}
//获取脉冲数
short int pulseCount = LPLD_LPTMR_GetPulseAcc(); //获取脉冲数
LPLD_LPTMR_ResetCounter(); //清空LPTMR
LPTMR在采集脉冲的时候相对于FTM、DMA来说是准确度最高的,但是只能LPTMR模块只能实现单路脉冲采集。
DMA使得外围设备可以通过DMA控制器直接访问内存,与此同时,CPU可以继续执行程序,相对于普通的IO中断时会向CPU发送中断请求显然节约了CPU的处理时间,也就是硬件中断,比软件中断和轮询都要好很多。而且K60有5组IO口,每组IO口都可以选择一个IO口作为DMA脉冲计数的IO口,大大的扩展了可计数的路数,但是实际上DMA采集到的脉冲数会时不时的有很大的跳变,虽然在一定程序上可以通过软件限幅解决,但是很影响采集精度,而且程序的可靠性也会受影响。(DMA其实也有出现在K60的中断表中,不知道是不是因为这个原因影响到了采集脉冲的总中断?)
下面给出来的代码是从野火那里移植过来的,LPLD本身并没有能够实现DMA脉冲计数的函数,使用野火的直接去找源码吧:
/*************************************************************************
* 野火嵌入式开发工作室
*
* 函数名称:DMA_count_Init
* 功能说明:DMA累加计数初始化
* 参数说明:DMA_CHn 通道号(DMA_CH0 ~ DMA_CH15)
* PTxn 触发端口
* count 累加计数中断值
* DMA_Count_cfg DMA传输配置
* 函数返回:无
* 修改时间:2012-1-20
* 备 注:
*************************************************************************/
void DMA_count_Init(uint8 CHn, PortPinsEnum_Type ptxn,uint32 count)
{
uint8 byten = DMA_BYTE1;
uint8 BYTEs=(byten==DMA_BYTE1 ? 1:(byten==DMA_BYTE2 ? 2:(byten==DMA_BYTE4 ? 4:16 ) ) ); //计算传输字节数
if(count > 0x7FFF )count = 0x7FFF;
count_init[CHn]=count;
/* 开启时钟 */
SIM->SCGC7|=SIM_SCGC7_DMA_MASK; //打开DMA模块时钟
SIM->SCGC6|=SIM_SCGC6_DMAMUX_MASK; //打开DMA多路复用器时钟
/* 配置 DMA 通道 的 传输控制块 TCD ( Transfer Control Descriptor ) */
DMA0->TCD[CHn].SADDR = DMA_SADDR_SADDR(COUNTSADDR); // 设置源地址
DMA0->TCD[CHn].DADDR = DMA_DADDR_DADDR(COUNTDADDR); // 设置目的地址
DMA0->TCD[CHn].SOFF = 0; // 设置源地址不变
DMA0->TCD[CHn].DOFF = 0; // 每次传输后,目的地址不变
DMA0->TCD[CHn].ATTR = (0
| DMA_ATTR_SMOD(0x0) // 源地址模数禁止 Source address modulo feature is disabled
| DMA_ATTR_SSIZE(byten) // 源数据位宽 :DMA_BYTEn 。 SSIZE = 0 -> 8-bit ,SSIZE = 1 -> 16-bit ,SSIZE = 2 -> 32-bit ,SSIZE = 4 -> 16-byte
| DMA_ATTR_DMOD(0x0) // 目标地址模数禁止
| DMA_ATTR_DSIZE(byten) // 目标数据位宽 :DMA_BYTEn 。 设置参考 SSIZE
);
DMA0->TCD[CHn].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(count); //当前主循环次数
DMA0->TCD[CHn].BITER_ELINKNO = DMA_CITER_ELINKNO_CITER(count);//起始主循环次数
DMA0->CR &=~0x80u; // CR[EMLM] = 0
DMA0->TCD[CHn].NBYTES_MLNO= DMA_NBYTES_MLNO_NBYTES(BYTEs); // 通道每次传输字节数,这里设置为BYTEs个字节。注:值为0表示传输4GB */
/* 配置 DMA 传输结束后的操作 */
DMA0->TCD[CHn].SLAST = -count; //调整 源地址的附加值,主循环结束后恢复 源地址
DMA0->TCD[CHn].DLAST_SGA = 0; //调整目的地址的附加值,主循环结束后恢复目的地址或者保持地址
DMA0->TCD[CHn].CSR = (0
| DMA_CSR_DREQ_MASK //主循环结束后停止硬件请求
| DMA_CSR_INTMAJOR_MASK //主循环结束后产生中断
);
/* 配置 DMA 触发源 */
DMAMUX->CHCFG[CHn] = (0
| DMAMUX_CHCFG_ENBL_MASK /* Enable routing of DMA request */
| DMAMUX_CHCFG_SOURCE((ptxn>>5)+PORTA_DMAREQ) /* 通道触发传输源: */
);
SIM->SCGC5 |= (SIM_SCGC5_PORTA_MASK<<(ptxn>>5)); //开启PORTx端口
/* 开启中断 */
LPLD_DMA_EnableReq(CHn); //使能通道CHn 硬件请求
enable_irq((IRQn_Type)(CHn + DMA0_IRQn)); //允许DMA通道传输
}
/*************************************************************************
* 野火嵌入式开发工作室
*
* 函数名称:DMA_count_get
* 功能说明:返回累加计数值
* 参数说明:DMA_CHn 通道号(DMA_CH0 ~ DMA_CH15)
* 函数返回:累加计数值
* 修改时间:2012-3-320
* 备 注:
*************************************************************************/
uint32 DMA_count_get(uint8 CHn)
{
uint32 temp = count_init[CHn] - DMA0->TCD[CHn].CITER_ELINKNO ;
return temp;
}
/*************************************************************************
* 野火嵌入式开发工作室
*
* 函数名称:DMA_count_reset
* 功能说明:重新加载累加计数值
* 参数说明:DMA_CHn 通道号(DMA_CH0 ~ DMA_CH15)
* 函数返回:累加计数值
* 修改时间:2012-3-320
* 备 注:
*************************************************************************/
void DMA_count_reset(uint8 CHn)
{
//设置主循环计数器 current major loop count
DMA0->TCD[CHn].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(count_init[CHn]);
}
//初始化DMA采集
void Count_Init(void)
{
GPIO_InitTypeDef DMA_GPIO;
DMA_GPIO.GPIO_PTx = PTC; //选择PORT
DMA_GPIO.GPIO_Pins = GPIO_Pin5; //选择要初始化的引脚
DMA_GPIO.GPIO_Dir = DIR_INPUT; //选择输入模式
DMA_GPIO.GPIO_PinControl = INPUT_PULL_UP|IRQC_DMAFA; //选择输入上拉、下降沿产生DMA请求
LPLD_GPIO_Init(DMA_GPIO); //初始化对应GPIO口
DMA_count_Init(DMA_CH0, PTC5,0x7FFF); //DMA脉冲计数初始化
}
//获取脉冲计数
int32 pulseCount = DMA_count_get(DMA_CH0); //采集脉冲数
DMA_count_reset(DMA_CH0); //清空
花了点时间整理了一下思路,当时既要考虑PWM输出、编码器计数、还有多路的脉冲采集真是想了好半天……