利用stm32两个定时器级联实现32位计数
stm32的定时器为16位,作计数器时可实现0~65535间的计数。若需要记录更大的数据,依靠单一定时器计数就无能为力了,必须寻找新的办法。一般方案有四种:1)直接更换具有32位定时器的MCU芯片;2)通过外围IC组合实现更多位数的计数电路;3)通过计数中断方式将16位扩展成32位计数;4)通过stm32主从定时器级联方式实现计数位数的倍增。
前两种方案均具大幅增加成本,后两种不增加硬件成本而实现计数位数的倍增,在很多场景下可做优选。后两种方案均需至少两个定时器做计数器。方案3可利用前置计数器对外部输入脉冲计数,在其溢出中断在改变某引脚输出电平,并将该引脚引到后置定时器的相应输入引脚,通过后置定时器计数前置定时器的溢出事件次数。在某一时刻同时读取两个定时器的计数值CNT1、CNT2,整体计数值CNT=CNT1+CNT2*2^32。该方案优势在于定时器及其通道选择非常灵活,对于较高频率的输入脉冲,前置定时器的中断频繁,软件开支过大。但stm32的定时器还有一项级联功能,即以主从定时器通过内部直联形式实现信息传递,方便的扩展计数位数,即方案4。
以PA0(TIM2_CH1_ETR)输入脉冲约5kHz,以TIM2为主定时器,TIM3为从定时器,以TIM7为1秒定时,代码如下:
//主定时器初始化
void TIM2_ETR_Counter_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStruct;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 初始化脉冲输出引脚PA0,若PA1输入脉冲将下面Pin_0改为Pin_1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//时基配置
TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//以上部分是相对通用的内容,以下部分十分关键
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 4); //外部时钟模式2设置:不分频,滤波4,PA0输入脉冲时
// TIM_TIxExternalClockConfig(TIM2, TIM_TIxExternalCLK1Source_TI2, TIM_ICPolarity_Rising, 0); //PA1输入脉冲时
TIM_SelectMasterSlaveMode(TIM2, TIM_MasterSlaveMode_Enable); //开启TIM2主从模式
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); //选择TIM2输出Trigger
TIM_SetCounter(TIM2, 0);
}
void TIM2_ETR_Counter_Start(void)
{
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_ETR_Counter_Stop(void)
{
TIM_Cmd(TIM2, DISABLE);
}
void TIM3_Slave_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_SelectInputTrigger(TIM3, TIM_TS_ITR1); // 选择TIM3输入Trigger
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_External1);
TIM_SetCounter(TIM3, 0);
}
void TIM3_Slave_Start(void)
{
TIM_Cmd(TIM3, ENABLE);
}
void TIM3_Slave_Stop(void)
{
TIM_Cmd(TIM3, DISABLE);
}
//以下函数初始化相关定时器并使能开始工作,在main函数调用一次就可持续计数
void GetPCMCount(void)
{
TIM7_Init_1s();
TIM2_ETR_Counter_Init();
TIM3_Slave_Init();
TIM7_1s_Start();
TIM2_ETR_Counter_Start();
TIM3_Slave_Start();
}
void TIM7_IRQHandler(void)
{
if(TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)
{
//读取TIM2、TIM3计数值,为方便调试写在进入中断开始
uint16_t tim2_cnt = TIM_GetCounter(TIM2);
uint16_t tim3_cnt = TIM_GetCounter(TIM3);
secondCount++;
//计算30秒整体计数值
if(secondCount == 30)
{
PCMCount = ((uint32_t) tim3_cnt << 16) | tim2_cnt;
TIM_SetCounter(TIM2, 0);
TIM_SetCounter(TIM3, 0);
secondCount = 0;
}
TIM_ClearITPendingBit(TIM7, TIM_IT_Update);
}
}
在TIM7中断函数中设置断点运行,30秒计数共146,928,大于65535(16位)。
以上利用TIM2的专用外部引脚PA0(TIM2_CH1_ETR),也可以用PA1(TIM2_CH2)输入脉冲信号。程序仅更改主定时器的初始函数中的GPIO及其时钟模式。
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 4); //外部时钟模式2:ETR输入
TIM_TIxExternalClockConfig(TIM2, TIM_TIxExternalCLK1Source_TI2, TIM_ICPolarity_Rising, 0); //外部时钟模式1:通道2输入
前一个函数对SMCR的ECE(External clock enable)置1,计数器由外部专用引脚ETR输入脉冲的有效沿控制计数;后一个函数对TIM2的SMCR置SMS=111即选择外部时钟模式1,置TS=110即触发选择通道2输入。
从定时器TIM3初始化中重要的语句为:
TIM_SelectInputTrigger(TIM3, TIM_TS_ITR1); // 选择TIM3输入Trigger
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_External1);
前者为TIM3选择输入触发源,也是对SMCR操作,具体是置TS=001,即选择internal trigger 1 (ITR1);后者置SMS=111即选择外部时钟模式1
。
使用主从定时器级联32位计数是通过stm32芯片内部的逻辑连接实现,要保证主定时器的溢出与从定时器的计数时钟可以连接,所以要严格参考stm32芯片数据手册确定正确的主从关系及Trigger Selection (TS)参数。从手册提供的定时器内部触发连接表可以看出,可以做从定时器的有TIM1~5和TIM8,每个从定时器可设置内部触发的主定时器均有4个,每个对应不同的编号。本例选择TIM2/3分别为主/从定时器,则TS必须设置为001。



以上程序在Geehy APM32E103VET6上运行成功。
更多推荐



所有评论(0)