定时器级联实现小数分频

用stm32的定时器输出任意频率方波,如果需要的频率不高,用主时钟分频一般就够了。例如需要755Hz的方波,36M/755=47682.119,只要把主时钟47682分频即可得到755.002Hz的输出. (实际主频72M,定时器按Toggle方式输出,这样可以保证方波占空比是50%.)

如果需要的频率比较高呢? 比如558kHz, 72M/64/2=562.5k, 72M/65/2=553.8k, 都还差得远. 如果能做到72M/64.516=558.001kHz, 这还差不多.

这种情况一般需要类似MC12016之类的双模分频器来实现, 后级计数器控制前面的双模分频器在/N和/N+1之间不断切换, 对于上面的情况, 就是后级计数器每计数到516个脉冲就把前级分频比改为65, 继续计数到1000个脉冲再把分频比改回64, 如此往复, 总的效果就是前级计数器64.516分频了. 当然, 这样输出的558kHz实际上是562.5k和553.8k不断切换的结果, 抖动很大, 有些场合能用, 有些场合不适合.

另外,采用stm32的定时器级联, 主定时器的输出可以作为从定时器的输入时钟, 这样只要给从定时器开一个比较匹配中断, 一个更新中断, 在两个中断里来回修改主定时器的ARR值, 不就实现了?

实际算一下, 这么做的问题很大. 当所需要的输出频率较高, 分频比又非常接近整数时, 两个中断之间的时间间隔就太短了, 前一个中断来不及处理完, 这样输出频率肯定不准. 幸好定时器的比较匹配事件和溢出事件都可以分别触发DMA传输, 这样从定时器用两个DMA通道, 分别在比较匹配和更新时写主定时器的ARR即可.

程序如下, TIM4和TIM5分别作为主/从定时器, TIM2用来测量频率. 调用TIMER_SetFreq函数即可设置输出频率. 实测输出几十kHz频率时的效果非常好, 输出3.58MHz仍然能工作, 只是抖动大到没法看了. (如果加上sigma-delta调制, 应该能改善一点, 不过估计很麻烦.)

再就是如果能用分数逼近来凑出所需分频比的小数部分, 应该比简单使用百位或者千位截断好一些. 有空找找分数逼近算法.

#include <math.h>
#include "misc.h"

static struct {
    unsigned short period_master_n;
    unsigned short period_master_n1;
    unsigned short period_slave;
    unsigned short comp_slave;
    unsigned long counter;
} g = {9, 10, 100, 5, 0};           // 从定时器周期设为100, 越大则输出频率越精确, 但抖动也更严重

void TIMER_SetFreq(unsigned long freq)
{
    g.period_master_n = (SystemCoreClock / 2) / freq - 1;
    g.period_master_n1 = g.period_master_n + 1;
    g.comp_slave = (int)round(
        ((SystemCoreClock / 2) % freq * g.period_slave * 1.0) / freq);
    TIM_SetCompare1(TIM5, g.comp_slave);
}

static void TIMER_MasterConfig(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_Period = g.period_master_n;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
    TIM_ARRPreloadConfig(TIM4, ENABLE);

    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
    TIM_OC1Init(TIM4, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
    TIM_SelectOutputTrigger(TIM4, TIM_TRGOSource_Update);
    TIM_Cmd(TIM4, ENABLE);
}

static void TIMER_SlaveConfig(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    TIM_DeInit(TIM5);
    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_TimeBaseStructure.TIM_Period = g.period_slave;
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
    TIM_ARRPreloadConfig(TIM5, ENABLE);

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;
    TIM_OCInitStructure.TIM_Pulse = g.comp_slave;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
    TIM_OC1Init(TIM5, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM5, TIM_OCPreload_Enable);

    TIM_SelectMasterSlaveMode(TIM5, TIM_MasterSlaveMode_Enable);
    TIM_SelectSlaveMode(TIM5, TIM_SlaveMode_External1);       // 从定时器:外部时钟源模式
    TIM_SelectInputTrigger(TIM5, TIM_TS_ITR2);      // TIM4触发TIM5
    TIM_Cmd(TIM5, ENABLE); // TIM5 enable counter
    TIM_DMACmd(TIM5, TIM_DMA_Update, ENABLE);         // 开启更新和1通道匹配触发DMA传输
    TIM_DMACmd(TIM5, TIM_DMA_CC1, ENABLE);

    DMA_StructInit(&DMA_InitStructure);
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = 1;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (unsigned long)&(TIM4->ARR); 
    DMA_InitStructure.DMA_MemoryBaseAddr = (unsigned long)&(g.period_master_n1);
    DMA_Init(DMA2_Channel2, &DMA_InitStructure);
    DMA_Cmd(DMA2_Channel2, ENABLE);
    DMA_InitStructure.DMA_MemoryBaseAddr = (unsigned long)&(g.period_master_n);
    DMA_Init(DMA2_Channel5, &DMA_InitStructure);
    DMA_Cmd(DMA2_Channel5, ENABLE);
}

static void TIMER_FreqMeasConfig(void)                     // 用T2_ETR来测量频率, 用飞线短接PB6和PA0
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TIM_DeInit(TIM2);
    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, 0);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_SetCounter(TIM2, 0);
    TIM_Cmd(TIM2, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    NVIC_EnableIRQ(TIM2_IRQn);
}

void TIM2_IRQHandler(void)         // 进位处理, stm32f103没有24位定时器, 不然就不用这么折腾了
{
    if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        g.counter += 65536;
    }
}

unsigned long TIMER_GetCounter(void)          // 读取频率计数和清零
{
    unsigned long ret = g.counter + TIM_GetCounter(TIM2);
    TIM_SetCounter(TIM2, 0);
    g.counter = 0;
    return ret;
}

void TIMER_Config(void)
{
    TIMER_MasterConfig();
    TIMER_SlaveConfig();
    TIMER_FreqMeasConfig();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值