前言

在这篇博客中工业相机:Flir Blackfly S—-配置多个摄像头进行同步拍摄方法介绍了如何通过主副相机进行多个摄像头同步拍摄。

还有一种方法可以实现多个摄像头同步拍摄,即通过外部触发的方式,所有摄像头的触发信号接同一触发源,即可严格的实现同步拍摄

本篇博客主要介绍 如何通过STM32F4 的 TIM14 来产生 PWM 输出,输出频率为20hz及25hz,高电平占比为周期前10%。实现同步信号的产生。

PWM 简介

PWM定义:
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制。

PWM是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。

简单一点,就是对脉冲宽度的控制,

PWM 原理如图
在这里插入图片描述
上面是一个 PWM 原理示意图。这个图一定要非常了解。
图中,假定定时器工作在向上计数 PWM模式,且当 CNT=CCRx 时输出 1。(具体小于输出0还是1 可以设置

那么就可以得到如上的 PWM示意图:当 CNT 值小于 CCRx 的时候,IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,IO 输出高电平(1),当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。

改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率,这就是 PWM 输出的原理

STM32F4 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出。这里我们仅使用 TIM14 的 CH1 产生一路 PWM 输出。

PWM寄存器

要使 STM32F4 的通用定时器 TIMx 产生 PWM 输出,会用到 3 个寄存器,来控制 PWM 的。

这三个寄存器分别是:

  • 捕获/比较模式寄存器(TIMx_CCMR1/2)
  • 捕获/比较使能寄存器(TIMx_CCER)
  • 捕获/比较寄存器(TIMx_CCR1~4)

简单介绍一下这三个寄存器:

1:捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有 2 个:TIMx _CCMR1和 TIMx _CCMR2。不过 TIM14 只有一个。TIMx_CCMR1 控制 CH1 和 2,而 TIMx_CCMR2控制 CH3 和 4。

以下我们将以 TIM14 为例进行介绍
TIM14_CCMR1 寄存器各位描述如图 所示:
在这里插入图片描述该寄存器的有些位在不同模式下,功能不一样。把寄存器分了 2层,上面一层对应输出而下面的则对应输入。

模式设置位 OC1M,此部分由 3位组成。总共可以配置成 7 种模式,
我们使用的是 PWM 模式,所以这 3 位必须设置为 110/111。110和111的这两种 PWM 模式的区别就是输出电平的极性相反。

CC1S 用于设置通道的方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。
注意:这里是因为我们的 TIM14 只有 1 个通道,所以才只有第八位有效,高八位无效,其他有多个通道的定时器,高八位也是有效的

2:TIM14 的捕获/比较使能寄存器(TIM14_CCER),该寄存器控制着各个输入输出通道的开关。该寄存器的各位描述如图 所示:
在这里插入图片描述
该寄存器比较简单,我们这里只用到了 CC1E 位,该位是输入/捕获 1 输出使能位,要想PWM 从 IO 口输出,这个位必须设置为 1,所以我们需要设置该位为 1。

同样,因为 TIM14 只有1 个通道,所以才只有低四位有效,如果是其他定时器,该寄存器的其他位也可能有效。

3:捕获/比较寄存器(TIMx_CCR1~4),该寄存器总共有 4 个,对应 4 个通道 CH1~4。不过 TIM14 只有一个,即:TIM14_CCR1,该寄存器的各位描述如图所示:
在这里插入图片描述
位15:0 CCR1[15:0]:捕获/比较 1 值
如果通道CC1 配置 为输出:
CCR1 为要装载到实际捕获/比较 1寄存器的值(预装载值)
如果没有通过TIMx_CCMR寄存器中的OC1PE位来使能预装载功能,写入的数值会被直接传输至当前寄存器中。否则只在发生更新事件时生效(拷贝到实际起作用的捕获/比较寄存器1)实际捕获/比较寄存器中包含要与计数器TIMx_CNT进行比较并在OC1输出上发出信号的值。

在输出模式下,该寄存器的值与 CNT 的值比较,根据比较结果产生相应动作。利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。

如果是通用定时器,则配置以上三个寄存器就够了,但是如果是高级定时器,则还需要配
置:刹车和死区寄存器(TIMx_BDTR),该寄存器各位描述如图所示:
在这里插入图片描述
该寄存器,我们只需要关注最高位:MOE 位,要想高级定时器的 PWM 正常输出,则必须设置 MOE 位为 1,否则不会有输出。

PWM实现方法

实现通过 TIM14_CH1输出 PWM,下面介绍通过库函数来配置该功能的步骤。

1 开启 TIM14 和 GPIO 时钟,配置 PF9 选择复用功能 AF9(TIM14)输出。

要使用 TIM14,我们必须先开启 TIM14 的时钟,还要配置 PF9 为复用(AF9)输出,才可以实现 TIM14_CH1 的 PWM 经过 PF9
输出。 库函数使能 TIM14 时钟的方法是:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE); //TIM14 时钟使能

还要使能 GPIOF 的时钟
配置 PF9 引脚映射至 AF9,复用为定时器 14,调用的函数为:

GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14); //GPIOF9 复用为定时器 14

最后设置 PF9 为复用功能输出,这里只列出 GPIO 初始化为复用功能的一行代码:

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能

对于定时器通道的引脚关系,可以查看 STM32F4 对应的数据手册,比如 PWM ,我们使用的是定时器 14 的通道 1,对应的引脚 PF9 可以从数据手册表中查看:
在这里插入图片描述

2 初始化 TIM14,设置 TIM14 的 ARR 和 PSC 等参数。

在开启了 TIM14 的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来控制输出 PWM的周期。
在库函数是通过 TIM_TimeBaseInit 函数实现的,调用的格式为:

TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化 TIMx 的

3 设置 TIM14_CH1 的 PWM 模式,使能 TIM14 的 CH1 输出。

设置 TIM14_CH1 为 PWM 模式(默认是冻结的)
在库函数中,PWM 通道
设置是通过函数 TIM_OC1Init()~TIM_OC4Init()来设置的,不同的通道的设置函数不一样,这里
我们使用的是通道 1,所以使用的函数是 TIM_OC1Init()。

void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

看结构体 TIM_OCInitTypeDef的定义:

typedef struct
{
 uint16_t TIM_OCMode; 
 uint16_t TIM_OutputState; 
 uint16_t TIM_OutputNState; */
 uint16_t TIM_Pulse; 
 uint16_t TIM_OCPolarity; 
 uint16_t TIM_OCNPolarity; 
 uint16_t TIM_OCIdleState; 
 uint16_t TIM_OCNIdleState; 
} TIM_OCInitTypeDef

这里讲解一下与要求相关的几个成员变量:

  • 参数 TIM_OCMode 设置模式是 PWM 还是输出比较,这里我们是 PWM 模式。
  • 参数 TIM_OutputState 用来设置比较输出使能,也就是使能 PWM 输出到端口。
  • 参数 TIM_OCPolarity 用来设置极性是高还是低。
  • 其他的参数 TIM_OutputNState,TIM_OCNPolarity,TIM_OCIdleState 和 TIM_OCNIdleState 是高级定时器才用到的。
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择模式 PWM
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性低
TIM_OC1Init(TIM14, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM1 4OC1

4 使能 TIM14

TIM_Cmd(TIM14, ENABLE); //使能 TIM14

5 修改 TIM14_CCR1 来控制占空比

经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改 TIM14_CCR1 则可以控制 CH1 的输出占空比。
在库函数中,修改 TIM14_CCR1 占空比的函数是:

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare2);

对于其他通道,分别有一个函数名字,函数格式为 TIM_SetComparex(x=1,2,3,4)

通过以上 5 个步骤,我们就可以控制 TIM14 的 CH1 输出 PWM 波了。

高级定时器虽然和通用定时器类似,但是高级定时器要想输出 PWM,必须还要设置一
个 MOE 位(TIMx_BDTR 的第 15 位),以使能主输出,否则不会输出 PWM。库函数设置的函数
为:

void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState)

PWM频率设置

通过PWM的原理可以知道,当定时器的频率固定时,PWM的周期只和ARR有关,定时器还可以设置一个分频系数,所以计算如下

TIM14的时钟为84M

分频系数为a, 重载值为b。
pwm频率为 84M/(a_b)
pwm周期为 (a_b)/84M

最开始说了,目的是产生25hz及20hz的信号,那么设置可以如下:
25hz:

        PWM_PSC = 84;//分频系数.----计数频率为84M/PWM_PSC = 1M hz
        PWM_ARR = 40000;//重装载值----PWM频率  (84M/PWM_PSC)/PWM_ARR = 25 hz

20hz:

        PWM_PSC = 84;//分频系数.----计数频率为84M/PWM_PSC = 1M hz
        PWM_ARR = 50000;//重装载值----PWM频率  (84M/PWM_PSC)/PWM_ARR = 25 hz

Code

下面给出关键部分代码。

//通过PWM产生相机拍照拍照信号
#define CAMERA_TRIGGER_20HZ 0
#define CAMERA_TRIGGER_25HZ 1

u32 PWM_PSC ;//PWM 预分频系数
u32 PWM_ARR ;//PWM 重装载值
float PWM_a;//PWM 占空比
uint32_t PWM_CCR;//PWM 比较值
int main(void)
{ 

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
    delay_init(168);  //初始化延时函数

  if(CAMERA_TRIGGER_25HZ)
    {
        PWM_PSC = 84;//分频系数.----计数频率为84M/PWM_PSC = 1M hz
        PWM_ARR = 40000;//重装载值----PWM频率  (84M/PWM_PSC)/PWM_ARR = 25 hz
        PWM_a = 0.1;//占空比    
    }else if(CAMERA_TRIGGER_20HZ)
    {
        PWM_PSC = 84;//分频系数.----计数频率为84M/PWM_PSC = 1M hz
        PWM_ARR = 50000;//重装载值----PWM频率  (84M/PWM_PSC)/PWM_ARR = 25 hz
        PWM_a = 0.1;//占空比    
    }

    PWM_CCR = (uint32_t)PWM_ARR*PWM_a;         
     TIM14_PWM_Init(PWM_ARR,PWM_PSC);    //设置PWM周期
    TIM_SetCompare1(TIM14,PWM_CCR);    //修改比较值,修改占空比

   while(1) 
    {
         //delay_ms(1);     
    }
}
//TIM14 PWM部分初始化 
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM14_PWM_Init(u32 arr,u32 psc)
{                              
    //此部分需手动修改IO口设置

    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE);      //TIM14时钟使能    
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);     //使能PORTF时钟    

    GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14); //GPIOF9复用为定时器14

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;           //GPIOF9
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        //复用功能
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;    //速度100MHz
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽复用输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;        //上拉
    GPIO_Init(GPIOF,&GPIO_InitStructure);              //初始化PF9

    TIM_TimeBaseStructure.TIM_Prescaler=psc-1;  //定时器分频
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
    TIM_TimeBaseStructure.TIM_Period=arr-1;   //自动重装载值
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; 

    TIM_TimeBaseInit(TIM14,&TIM_TimeBaseStructure);//初始化定时器14

    //初始化TIM14 Channel1 PWM模式     
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2
     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
    TIM_OC1Init(TIM14, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM1 4OC1

    TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);  //使能TIM14在CCR1上的预装载寄存器

  TIM_ARRPreloadConfig(TIM14,ENABLE);//ARPE使能 

    TIM_Cmd(TIM14, ENABLE);  //使能TIM14


}

Result

通过示波器查看两个信号
25hz信号如下:
在这里插入图片描述
20hz信号如下:
在这里插入图片描述