4、定时器与PWM生成:基础定时器配置、高级定时器PWM模式、占空比与频率控制、用于RF脉冲包络调制

各位同学,咱们今天聊定时器。说实话,在MRI射频脉冲序列的嵌入式实现里,定时器就是整个系统的「心跳」。没有它,你连一个像样的RF脉冲都发不出来。

我个人习惯把定时器分成两类来看:一类是干杂活的「基础定时器」,另一类是干精细活的「高级定时器」。在RF脉冲包络调制这个场景下,两者缺一不可。

4.1 基础定时器配置:别小看这个「计时员」

基础定时器能干什么?说白了就是计时、计数、触发中断。在RF脉冲序列里,它通常用来控制脉冲的持续时间、重复间隔,或者给ADC采样提供时间基准。

我刚开始做MRI项目时,觉得基础定时器太简单了,随便配配就行。结果有一次,脉冲宽度差了几个微秒,整个序列的k空间填充就乱了。嗯,从那以后我再也不敢轻视它。

配置基础定时器,核心就三步:

  • 时钟源选择:一般用内部时钟,分频系数要算清楚。比如系统时钟80MHz,你要产生1ms的中断,预分频器设成7999,自动重装载值设成9999——这样计数器每跳一次就是0.1μs,1ms刚好10000个计数。
  • 计数模式:向上计数最常用。但如果你需要产生对称的PWM波形,可以考虑中心对齐模式。
  • 中断使能:更新中断要打开,否则定时器溢出了你都不知道。

核心要点:基础定时器的精度取决于时钟源和预分频系数。MRI序列对时间精度要求高,建议使用高精度晶振,别用内部RC振荡器——温度漂移会让你抓狂。

// 基础定时器配置示例(STM32 HAL库风格)
void MX_TIM6_Init(void) {
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    htim6.Instance = TIM6;
    htim6.Init.Prescaler = 7999;      // 80MHz / (7999+1) = 10kHz
    htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim6.Init.Period = 9999;         // 10kHz / (9999+1) = 1Hz
    htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_Base_Init(&htim6);
    
    // 使能更新中断
    HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
}

4.2 高级定时器PWM模式:RF包络调制的核心武器

基础定时器只能计时,但高级定时器能直接输出PWM波形。在RF脉冲序列里,PWM用来干什么?调制包络。

你想想看,一个RF脉冲不能是方波直接怼上去,那样频谱会乱成一锅粥。我们需要一个平滑的包络——比如sinc形状、高斯形状。高级定时器的PWM模式,配合DMA或比较器,就能精确生成这些包络。

高级定时器比基础定时器多了几个关键功能:

  • 多路独立通道:每个通道可以独立配置PWM输出,占空比互不影响。
  • 互补输出与死区插入:如果你用H桥驱动RF线圈,这个功能是必须的。我曾经因为死区时间设得太短,导致上下管直通,烧了一个驱动板……教训深刻。
  • 刹车功能:紧急情况下可以强制关闭PWM输出,保护硬件。

我的经验:配置高级定时器的PWM模式时,先确认TIMx的时钟树。有些高级定时器挂在APB2上,频率可能和APB1不一样。别配了半天发现频率不对,那可就尴尬了。

4.3 占空比与频率控制:精度决定一切

占空比和频率,是PWM的两个核心参数。在RF包络调制中,频率决定了载波的位置,占空比决定了包络的幅度。

怎么控制?高级定时器里,频率由ARR(自动重装载寄存器)决定,占空比由CCR(捕获比较寄存器)决定。公式很简单:

  • 频率 = 定时器时钟 / (预分频系数 + 1) / (ARR + 1)
  • 占空比 = CCR / (ARR + 1) × 100%

但实际项目中,你往往需要动态调整占空比——比如生成一个sinc包络,每个PWM周期的占空比都不一样。这时候就要用到DMA了。

我记得有个项目,需要在10ms内输出一个128点的sinc包络。如果每个点都靠CPU去更新CCR,中断频率太高,CPU根本忙不过来。后来我用DMA把占空比数据表直接灌到CCR寄存器里,CPU全程不参与,完美解决。

注意:动态调整占空比时,要小心「影子寄存器」的更新时机。默认情况下,CCR的值会在更新事件发生时才生效。如果你需要立即生效,可以关闭预装载功能,但要注意可能产生不完整的PWM周期。

// 使用DMA更新占空比(生成sinc包络)
uint16_t sinc_table[128];  // 预先计算好的占空比数据

void PWM_Sinc_Start(void) {
    // 配置DMA:从内存到TIM1的CCR1寄存器
    HAL_DMA_Start(&hdma_tim1_ch1, (uint32_t)sinc_table, 
                  (uint32_t)&TIM1->CCR1, 128);
    
    // 使能TIM1的DMA请求
    __HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_CC1);
    
    // 启动PWM输出
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
}

4.4 用于RF脉冲包络调制:实战中的坑与解

好了,前面都是基础。现在咱们聊聊RF脉冲包络调制的实战。

RF脉冲的包络,本质上是一个基带信号。我们用PWM的占空比变化来模拟这个基带信号,然后通过射频开关或混频器把它搬到载波频率上。

具体怎么做?我一般分三步:

  1. 计算包络数据:根据序列需求,生成sinc、高斯或矩形包络的离散点。每个点对应一个PWM周期的占空比。
  2. 配置高级定时器:设置好频率(通常几百kHz到几MHz),使能PWM输出,配置DMA传输。
  3. 同步触发:用另一个定时器或外部触发信号,精确控制脉冲的起始和结束时间。

这里有个坑,我踩过好几次:包络的采样率要和PWM频率匹配。比如PWM频率是1MHz,那你每个PWM周期只能更新一次占空比。如果包络需要更高的时间分辨率,就得提高PWM频率——但频率太高,占空比的精度又会下降。这是个trade-off。

避坑指南:我曾经在调试一个3T MRI的射频脉冲时,发现包络形状总是有毛刺。查了两天才发现,是DMA传输的优先级太低,被其他中断打断了。解决方案:把DMA优先级提到最高,或者用双缓冲DMA。

另外,别忘了考虑死区时间。如果你用H桥或推挽结构驱动RF线圈,死区时间会影响包络的线性度。我一般会在占空比数据里预先补偿死区的影响——虽然麻烦,但效果立竿见影。

最后说一句:调试PWM包络时,示波器是你的好朋友。别光看代码,把PWM输出引出来,看看实际波形和理论包络是否一致。很多时候,问题出在硬件延迟上,而不是软件。

好,这一章就到这里。下一章咱们聊聊ADC采样与触发同步——那又是另一个精彩的故事了。