stm32之PWM呼吸灯

news2024/10/3 3:39:37

        呼吸灯是灯从渐亮到渐灭周而复始形成的一个效果。由于51没有PWM所以需要定时器模拟PWM才能实现呼吸灯的效果,但是stm32的通用定时器是有PWM模式的,所以不需要再用软件模拟,精准度也高。

本实验用的基于stm32f103C8t6。在PB8引脚上接了一个led, led的另一端接到vcc上。

PB8除了是一个GPIO功能,还有一个复用功能即定时器4的channel 3功能。可以通过参考手册知晓。

一、利用CubeMX生成代码

具体配置就不细说了,这里将TIM4的关键配置标了出来

记得选中PWM 的模式1 和使能比较输出,CH Polarity设置Low 和 High 在呼吸灯这里无影响。

1.1、计数器配置

时钟的溢出配置公式如下:

这里将定时器设置为500ms,即Tout = 500ms,同时PSC = 71,ARR = 499, Tclk = 72MHZ。根据公式计算出Tout = (71 + 1) * (499 + 1) / 72000000 = 500ms。

1.2、main函数代码配置

int main(void)
{
	uint16_t pwmVal = 0;
	uint8_t dir = 1;

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM4_Init();
  /* USER CODE BEGIN 2 */	
  // 开启定时器4
  HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_3);

  while (1)
  {
		
		HAL_Delay(1);
		if(dir) {
			pwmVal ++;
		} else {
			pwmVal--;
		}
		if(pwmVal > 500) {
			dir = 0;
		} else if(pwmVal <= 0) {
			dir = 1;
		}
		__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);
/*		
		// 常亮
   __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 0); 
	 // 常灭
		__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 500); 
		*/
  }
}

二、PWM分析

        输出比较就是通过定时器的外部引脚对外输出控制信号,有八种模式,由寄存器 CCMRx 的位 OCxM[2:0]控制。
  1. 000:冻结。输出比较寄存器TIMx_CCR1与计数器TIMx_CNT间的比较对OC1REF不起作用;
  2. 001 :匹配时设置通道 1 为有效电平。当计数器 TIMx_CNT 的值与TIMx_CCR1相同时,强制OC1REF为高。
  3. 010 :匹配时设置通道 1 为无效电平。当计数器 TIMx_CNT 的值与TIMx_CCR1相同时,强制OC1REF为低。
  4. 011:翻转。当TIMx_CCR1=TIMx_CNT时,翻转OC1REF的电平。
  5. 100:强制为无效电平。强制OC1REF为低。
  6. 101:强制为有效电平。强制OC1REF为高。
  7. 110PWM模式1- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电(OC1REF=0),否则为有效电平(OC1REF=1)
  8. 111PWM模式2- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平。
其中 PWM 模式是输出比较中的特例,使用的也最多。在PWM的模式1或2下,会一直进行 TIMx_CNT和TIMx_CCRx的比较。
  • PWM中GPIO引脚电平输出是由OCx来决定的而不是由OCxREF来决定的。
  • 正常GPIO的引脚电平输出由寄存器ODR来决定的(可以配置BSRR来决定ODR的输出)

下图是捕获/比较的输出阶段:

根据上图可以推出四种结果分别是

2.1、有效电平

PWM模式1

  • 在向上计数时,一旦TIMx_CNT < TIMx_CCR1时通道1为有效电平,否则为无效电平;
  • 在向下计数时,一旦TIMx_CNT > TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)
  • TIMx_CCRx > TIMx_ARR时 OCxREF = 1
  • TIMx_CCRx = 0时OCxREF = 0

PWM模式2

  • 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平
  • 在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电

上图中绿框部分是有效的电平。这里有有效电平是OCxREF 参考电平。

OCx有效电平

手册中还有另外一个描述就是:

The output stage generates an intermediate waveform which is then used for reference:
OCxRef (active high). The polarity acts at the end of the chain.

翻译一下就是:

输出部分产生一个中间波形OCxRef(高有效)作为基准,链的末端决定最终输出信号的极性。

“链的末端决定最终输出信号的极性” 怎么解释,这个可以通过CCIP位的说明可以看出来。
CC1P : Capture/Compare 1 output polarity
CC1 channel configured as output:
  • 0: OC1 active high.
  • 1: OC1 active low.

CC1通道作为输出模式

  • 当CCIP = 0时,OC1 = 1是有效电平,
  • 当CCIP = 1时,OC1 = 0是有效电平

2.2、PWM功能下GPIO引脚输出电平

在参考文档中有这样一个表格:

CCxE = 0时,禁止OCx输出,CCxE = 1时,OCx = OCxREF + Polarity

这里的OCxREF + Polarity是什么意思。这里先说明下这里是xor(异或)的意思。

我们可以从以下分析出:

在参考文档中的TIM1定时器章节有这样一个表格:

红色框中圈住的部分写出了OCx = OCxREF xor CCxP,当然这个表格是在TIM1和TIM8里出现的,像表格中的MOE,OSSI,OSSR,CCxNE,都是在TIM1和TIM8寄存器中存在的,在通用定时器里是没有的。

MOE,OSSI,OSSR存在于TIM1和TIM8寄存器中的BDTR,CCxNP和CCxNE也只存在于TIM1和TIM8定时器中的CCER寄存器。

TIM1和TIM8中的CCER

通用定时器中的CCER (reserved部分要保持为0,即保持reset时的值)

在通用定时器里面,OSSR 无效, CCxNE = 0, OSSI无效,MOE 无效,所以异或操作还是适用的。

  • 当CCIP = 0时,OC1 = 1是有效电平,
  • 当CCIP = 1时,OC1 = 0是有效电平

和 OCx = OCxREF xor CCxP

得出以下最终结果(绿色部分为有效输出):

总结:

  • 1、PWM的模式用来区分有效电平在哪个区间输出 ,并不能区分是有效电平是高还是低
  • 2、有效的电平的输出OCx由CCER寄存器的CCxP位来决定。
  • 3、CCxP = 0时(默认),输出与OCxREF相同的波
  • 4、CCxP = 1时,输出与OCxREF相反的波

三、代码分析

PWM的主要流程大致如下:

  1. 初始化TIM4
  2. 开启TIM4的PWM模式
  3. 设置CCR1用于动态配置PWM波形的输出

 代码主要是根据 定时器4的channel 3 + 向上计数模式 + 500ms 定时周期 这个为中心产生的。定时器涉及的寄存器比较多,定时总共有20种寄存器,在PWM输出模式下,用到的其实并不多。涉及的寄存器如下:

CR1 (control register)

CR2 (control register)

SMCR (slave mode control register)

EGR (event generation register)

 CCMR (capture/compare mode register 2 )

CCER (capture/compare enable register )

下面三个主要用来装载数据和配置无关

CNT (counter)

ARR (auto-reload register )

CCR3 (capture/compare register 3 )

3.1、MX_TIM4_Init

函数比较长,大致将功能分了下类,具体函数如下:

void MX_TIM4_Init(void)
{

	// 这里主要是根据功能将寄存器分成几个模块进行配置
	// 时钟相关配置
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
	// 主从模式配置
  TIM_MasterConfigTypeDef sMasterConfig = {0};
	// 定时器输出捕获常规配置
  TIM_OC_InitTypeDef sConfigOC = {0};

  htim4.Instance = TIM4;
  htim4.Init.Prescaler = 71;
  htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim4.Init.Period = 499;
  htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
	
	// 1、定时器常规初始化
  if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
	
	// 2、定时器时钟配置
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
	
	// 3、定时器PWM初始化
  if (HAL_TIM_PWM_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
	
	// 4、定时器主从模式配置
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
	// 5、定时器channel配置
  if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
  {
    Error_Handler();
  }
	
	// 6、定时器主栈地址初始化
  HAL_TIM_MspPostInit(&htim4);

}

上面主要做了下面几件事

  • 1、定时器常规初始化(时基单元相关)
  • 2、定时器时钟配置
  • 3、定时器PWM初始化
  • 4、定时器主从模式配置
  • 5、定时器channel配置
  • 6、定时器主栈地址初始化(实际上就是使能定时器)

下面主要针对上面的过程进行描述

3.1.1、定时器相关类型

TIM_TypeDef

typedef struct
{
  __IO uint32_t CR1;             /*!< TIM control register 1,                      Address offset: 0x00 */
  __IO uint32_t CR2;             /*!< TIM control register 2,                      Address offset: 0x04 */
  __IO uint32_t SMCR;            /*!< TIM slave Mode Control register,             Address offset: 0x08 */
  __IO uint32_t DIER;            /*!< TIM DMA/interrupt enable register,           Address offset: 0x0C */
  __IO uint32_t SR;              /*!< TIM status register,                         Address offset: 0x10 */
  __IO uint32_t EGR;             /*!< TIM event generation register,               Address offset: 0x14 */
  __IO uint32_t CCMR1;           /*!< TIM  capture/compare mode register 1,        Address offset: 0x18 */
  __IO uint32_t CCMR2;           /*!< TIM  capture/compare mode register 2,        Address offset: 0x1C */
  __IO uint32_t CCER;            /*!< TIM capture/compare enable register,         Address offset: 0x20 */
  __IO uint32_t CNT;             /*!< TIM counter register,                        Address offset: 0x24 */
  __IO uint32_t PSC;             /*!< TIM prescaler register,                      Address offset: 0x28 */
  __IO uint32_t ARR;             /*!< TIM auto-reload register,                    Address offset: 0x2C */
  __IO uint32_t RCR;             /*!< TIM  repetition counter register,            Address offset: 0x30 */
  __IO uint32_t CCR1;            /*!< TIM capture/compare register 1,              Address offset: 0x34 */
  __IO uint32_t CCR2;            /*!< TIM capture/compare register 2,              Address offset: 0x38 */
  __IO uint32_t CCR3;            /*!< TIM capture/compare register 3,              Address offset: 0x3C */
  __IO uint32_t CCR4;            /*!< TIM capture/compare register 4,              Address offset: 0x40 */
  __IO uint32_t BDTR;            /*!< TIM break and dead-time register,            Address offset: 0x44 */
  __IO uint32_t DCR;             /*!< TIM DMA control register,                    Address offset: 0x48 */
  __IO uint32_t DMAR;            /*!< TIM DMA address for full transfer register,  Address offset: 0x4C */
  __IO uint32_t OR;              /*!< TIM option register,                         Address offset: 0x50 */
}TIM_TypeDef;

TIM_TypeDef 结构体包括了定时器所有的寄存器,通过操作结构体就可以操作寄存器。在初始化的时候有这样一句代码 htim4.Instance = TIM4 这里的TIM4就是定时器4在外设中的地址,TIM4也是一个宏,具体就不展开了,它的定义和 GPIO类似,可参考GPIO,或自行在代码中查看。

TIM_HandleTypeDef

typedef struct
{
  uint32_t Prescaler;        // 配置时基单元中的预分频器    
  uint32_t CounterMode;      // 计数模式(向上/向下/中央对齐)
  uint32_t Period;           // 定时周期(period + 1)
  uint32_t ClockDivision;    // 时钟分频因子
  uint32_t RepetitionCounter; // 重复定时器(高级定时器中用)
  uint32_t AutoReloadPreload; // 是否自动重装初值
} TIM_Base_InitTypeDef;

HAL_TIM_ActiveChannel (选中的channel)

typedef enum
{
  HAL_TIM_ACTIVE_CHANNEL_1        = 0x01U,    /*!< The active channel is 1     */
  HAL_TIM_ACTIVE_CHANNEL_2        = 0x02U,    /*!< The active channel is 2     */
  HAL_TIM_ACTIVE_CHANNEL_3        = 0x04U,    /*!< The active channel is 3     */
  HAL_TIM_ACTIVE_CHANNEL_4        = 0x08U,    /*!< The active channel is 4     */
  HAL_TIM_ACTIVE_CHANNEL_CLEARED  = 0x00U     /*!< All active channels cleared */
} HAL_TIM_ActiveChannel;

TIM_HandleTypeDef(保存定时器相关配置,状态和方法,下面进行了精简)

{
  TIM_TypeDef                        *Instance;  // 定时器寄存器集合       
  TIM_Base_InitTypeDef               Init;       // 定时器基本配置       
  HAL_TIM_ActiveChannel              Channel;     //使用的channel
  DMA_HandleTypeDef                  *hdma[7];          
  HAL_LockTypeDef                    Lock;          //是否进行锁定,配置完成之后都要进行锁定    
  __IO HAL_TIM_StateTypeDef          State;          //定时器状态   
  __IO HAL_TIM_ChannelStateTypeDef   ChannelState[4];   // channel的状态,总共有四个channel
  __IO HAL_TIM_ChannelStateTypeDef   ChannelNState[4];  
  __IO HAL_TIM_DMABurstStateTypeDef  DMABurstState;     

  // 函数指针就写了一个,其它的看源码哈
  void (* Base_MspInitCallback)(struct __TIM_HandleTypeDef *htim); 
} TIM_HandleTypeDef;
3.1.2、定时器常规初始化
下面的代码也进行了精简,方便看主要的过程。
HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
{
  /* Check the TIM handle allocation */
  if (htim == NULL)
  {
    return HAL_ERROR;
  }

  // 结构体初始化时未设置,默认是RESET状态
  if (htim->State == HAL_TIM_STATE_RESET)
  {
    htim->Lock = HAL_UNLOCKED;
    // 由于未注册定时器回调,这里把回调相关的方法删除了
    HAL_TIM_Base_MspInit(htim);
  }
    
  // 设置busy状态,防止操作定时器
  htim->State = HAL_TIM_STATE_BUSY;
    
  // 将Init中的配置同步到Tim4的寄存器中
  TIM_Base_SetConfig(htim->Instance, &htim->Init);
   
  // 未涉及DMA
  htim->DMABurstState = HAL_DMA_BURST_STATE_READY;
   
  //将四个channel设置成ready
  TIM_CHANNEL_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
  //将四个互补channel设置成ready(暂时无用)
  TIM_CHANNEL_N_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);

  // 设置就绪状态
  htim->State = HAL_TIM_STATE_READY;

  return HAL_OK;
}

初始化核心代码是TIM_Base_SetConfig这个函数,具体实现如下

void TIM_Base_SetConfig(TIM_TypeDef *TIMx, const TIM_Base_InitTypeDef *Structure)
{
  uint32_t tmpcr1;
  tmpcr1 = TIMx->CR1;

  // 只要是TIM1-4 就会成立(这是一个简单的宏)
  if (IS_TIM_COUNTER_MODE_SELECT_INSTANCE(TIMx))
  {
    // 清除CR1寄存器中的DIR 和CMS位
    // DIR是CR1中的第4位,CMS是5 6 位
    // DIR = 10000b  CMS = 1100000
    // 下面的意思是将DIR和CMS清0
    tmpcr1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);
    // 重新设置计数模式, 这里只设置了DIR,CMS保持00,00状态就是边沿对齐模式(向上或向下)
    // 下面的意思就是设置了边沿对齐的向上计数模式
    tmpcr1 |= Structure->CounterMode;
  }

  // 只要是TIM1-4 就会成立(这是一个简单的宏)
  if (IS_TIM_CLOCK_DIVISION_INSTANCE(TIMx))
  {
    // 清除时钟分频因子TIM_CR1_CKD = 1100000000b,下面就是清除CKD
    tmpcr1 &= ~TIM_CR1_CKD;
    // 重新配置时钟分频因子,本安全中外部传入的是0
    tmpcr1 |= (uint32_t)Structure->ClockDivision;
  }

  // 这里先清除CR1中ARPE位,然后根据AutoReloadPreload配置,就是是否使能自动重装初值
  MODIFY_REG(tmpcr1, TIM_CR1_ARPE, Structure->AutoReloadPreload);
  // 配置CR1 寄存器
  TIMx->CR1 = tmpcr1;

  // 配置ARR自动重装寄存器
  TIMx->ARR = (uint32_t)Structure->Period ;

  // 配置PSC寄存器
  TIMx->PSC = Structure->Prescaler;
  
  //TIM1 才有效
  if (IS_TIM_REPETITION_COUNTER_INSTANCE(TIMx))
  {
    /* Set the Repetition Counter value */
    TIMx->RCR = Structure->RepetitionCounter;
  }
  // 配置事件产生寄存器UG代码第0位,数值1代表定时器溢出时会产生更新事件
  TIMx->EGR = TIM_EGR_UG;
}

上面的代码主要是设置CR1、ARR、PSC、EGR和RCR(TIM1才有效)寄存器。

3.1.3、定时器时钟配置

项目中用到的是内部时钟,所以代码简化如下,这个函数主要处理SMCR寄存器的配置。

HAL_StatusTypeDef HAL_TIM_ConfigClockSource(TIM_HandleTypeDef *htim, const TIM_ClockConfigTypeDef *sClockSourceConfig)
{
  HAL_StatusTypeDef status = HAL_OK;
  uint32_t tmpsmcr;

  __HAL_LOCK(htim);

  htim->State = HAL_TIM_STATE_BUSY;

  tmpsmcr = htim->Instance->SMCR;
  // 下面的意思重置从模式寄存器所有位除了MSM位,
  tmpsmcr &= ~(TIM_SMCR_SMS | TIM_SMCR_TS);
  tmpsmcr &= ~(TIM_SMCR_ETF | TIM_SMCR_ETPS | TIM_SMCR_ECE | TIM_SMCR_ETP);
  htim->Instance->SMCR = tmpsmcr;

  htim->State = HAL_TIM_STATE_READY;

  __HAL_UNLOCK(htim);

  return status;
}
3.1.4、定时器PWM初始化

由于未开启PWM回调, 这里的操作和定时器常规初始化几乎一样

HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim)
{

  if (htim->State == HAL_TIM_STATE_RESET)
  {
    htim->Lock = HAL_UNLOCKED;

// 这里的条件不成立
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
    /* Reset interrupt callbacks to legacy weak callbacks */
    TIM_ResetCallback(htim);

    if (htim->PWM_MspInitCallback == NULL)
    {
      htim->PWM_MspInitCallback = HAL_TIM_PWM_MspInit;
    }
    htim->PWM_MspInitCallback(htim);
#else
    /* Init the low level hardware : GPIO, CLOCK, NVIC and DMA */
		// 这里是一个空操作
    HAL_TIM_PWM_MspInit(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
  }

  /* Set the TIM state */
  htim->State = HAL_TIM_STATE_BUSY;

  // 重新走了一下定时器的配置
  TIM_Base_SetConfig(htim->Instance, &htim->Init);

  htim->DMABurstState = HAL_DMA_BURST_STATE_READY;

  TIM_CHANNEL_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
  TIM_CHANNEL_N_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);

  htim->State = HAL_TIM_STATE_READY;

  return HAL_OK;
}
3.1.5、定时器主从模式配置

代表也非常简单,大致如下:

HAL_StatusTypeDef HAL_TIMEx_MasterConfigSynchronization(TIM_HandleTypeDef *htim,
                                                        const TIM_MasterConfigTypeDef *sMasterConfig)
{
  uint32_t tmpcr2;
  uint32_t tmpsmcr;
  __HAL_LOCK(htim);
  htim->State = HAL_TIM_STATE_BUSY;

  tmpcr2 = htim->Instance->CR2;

  tmpsmcr = htim->Instance->SMCR;

  // 清除CR2寄存器中的MMS位,即 4 5 6 都是0
  tmpcr2 &= ~TIM_CR2_MMS;
  // 设置新的MMS主模式选择
  tmpcr2 |=  sMasterConfig->MasterOutputTrigger;

  // 将CR2配置到寄存器中
  htim->Instance->CR2 = tmpcr2;
  
  if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
  {
    // 清除SMCR中的msm(主从模式选择)
    tmpsmcr &= ~TIM_SMCR_MSM;
    // 外部传入的是DISABLE	= 0,0代表无作用
    tmpsmcr |= sMasterConfig->MasterSlaveMode;
	  // 设置回寄存器
    htim->Instance->SMCR = tmpsmcr;
  }

  htim->State = HAL_TIM_STATE_READY;

  __HAL_UNLOCK(htim);

  return HAL_OK;
}
3.1.6、定时器主从模式配置

选调用 HAL_TIM_PWM_ConfigChannel,内部对channel3的处理如下


     TIM_OC3_SetConfig(htim->Instance, sConfig);

      /* Set the Preload enable bit for channel3 */
      htim->Instance->CCMR2 |= TIM_CCMR2_OC3PE;

      /* Configure the Output Fast mode */
      htim->Instance->CCMR2 &= ~TIM_CCMR2_OC3FE;
      htim->Instance->CCMR2 |= sConfig->OCFastMode;
      break;

核心代码 是TIM_OC3_SetConfig函数

static void TIM_OC3_SetConfig(TIM_TypeDef *TIMx, const TIM_OC_InitTypeDef *OC_Config)
{
  uint32_t tmpccmrx;
  uint32_t tmpccer;
  uint32_t tmpcr2;

  tmpccer = TIMx->CCER;

  // 清除CCE使能位
  TIMx->CCER &= ~TIM_CCER_CC3E;

  tmpcr2 =  TIMx->CR2;
  tmpccmrx = TIMx->CCMR2;

	// 清除CCMR2(输入捕获寄存器)0C3M(输出比较3模式),CC3S(捕获比较3选择)
  tmpccmrx &= ~TIM_CCMR2_OC3M;
  tmpccmrx &= ~TIM_CCMR2_CC3S;
  // 外部设置的 TIM_OCMODE_PWM1即110 0000(向上计数模式)
  tmpccmrx |= OC_Config->OCMode;

  // 清除CCER(输入捕获寄存器)极性位
  tmpccer &= ~TIM_CCER_CC3P;
  // 外部传入的HIGH = 0,CC3E = 0 禁止输出
  tmpccer |= (OC_Config->OCPolarity << 8U);

  /* Write to TIMx CR2 */
  TIMx->CR2 = tmpcr2;

  /* Write to TIMx CCMR2 */
  TIMx->CCMR2 = tmpccmrx;

  /* Set the Capture Compare Register value */
  TIMx->CCR3 = OC_Config->Pulse;

  /* Write to TIMx CCER */
  TIMx->CCER = tmpccer;
}

3.1.7、主栈地址初始化

里面就是就是使能了一下timer定时器

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM4)
  {
    /* TIM4 clock enable */
    __HAL_RCC_TIM4_CLK_ENABLE();
  }
}

3.2、开启TIM4的PWM模式

HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
{
  uint32_t tmpsmcr;

  /* Check the parameters */
  assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));

  /* Check the TIM channel state */
  if (TIM_CHANNEL_STATE_GET(htim, Channel) != HAL_TIM_CHANNEL_STATE_READY)
  {
    return HAL_ERROR;
  }

  // 将channel设置成busy(这里传入的是channel3)
  TIM_CHANNEL_STATE_SET(htim, Channel, HAL_TIM_CHANNEL_STATE_BUSY);

  // 使能channel 3
  TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);

	// TIM1才会进
  if (IS_TIM_BREAK_INSTANCE(htim->Instance) != RESET)
  {
    /* Enable the main output */
    __HAL_TIM_MOE_ENABLE(htim);
  }

  /* Enable the Peripheral, except in trigger mode where enable is automatically done with trigger */
  if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
  {
		// 获取SMCR 中0-2位(SMS) ,外部SMS是关闭的即0
    tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
		
		// 这个比较不成功 000 != 110
    if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
    {
			// 使能CR1的第0位CEN开启计数
      __HAL_TIM_ENABLE(htim);
    }
  }
  else
  {
    __HAL_TIM_ENABLE(htim);
  }

  /* Return function status */
  return HAL_OK;
}

核心代码就是使能通道3

void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel, uint32_t ChannelState)
{
  uint32_t tmp;

  /* Check the parameters */
  assert_param(IS_TIM_CC1_INSTANCE(TIMx));
  assert_param(IS_TIM_CHANNELS(Channel));

	// 外部是Channel3 = 1000 ,Channel & 0x1FU = 1000,
  tmp = TIM_CCER_CC1E << (Channel & 0x1FU); /* 0x1FU = 31 bits max shift */

  // 清除CC3E
  TIMx->CCER &= ~tmp;

  // 这里ChannelState = Enable = 1, 使能CC3E
  TIMx->CCER |= (uint32_t)(ChannelState << (Channel & 0x1FU)); /* 0x1FU = 31 bits max shift */
}

3.3、设置CCR1用于动态配置PWM波形的输出

更改CCR3,来设置占空比。

#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \
  (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCR1 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCR2 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCR3 = (__COMPARE__)) :\
   ((__HANDLE__)->Instance->CCR4 = (__COMPARE__)))

#define __HAL_TIM_SetCompare            __HAL_TIM_SET_COMPARE

__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);

四、总结

1、HAL 中每次在设置相应的位时都会先清除一下,清除时设置的宏对应位是1。

tmpcr1 &= ~TIM_CR1_CKD;
tmpcr1 |= (uint32_t)Structure->ClockDivision;

2、定时器单线程初始化时通常会加锁,完成之后解锁(别忘解锁)。

 __HAL_LOCK(htim);

__HAL_UNLOCK(htim);

// 加锁时会判断有没有锁住,没有锁住再加锁,有锁就直接返回
#define __HAL_LOCK(__HANDLE__)                                           \
                                do{                                        \
                                    if((__HANDLE__)->Lock == HAL_LOCKED)   \
                                    {                                      \
                                       return HAL_BUSY;                    \
                                    }                                      \
                                    else                                   \
                                    {                                      \
                                       (__HANDLE__)->Lock = HAL_LOCKED;    \
                                    }                                      \
                                  }while (0U)
// 返回时直接解锁
#define __HAL_UNLOCK(__HANDLE__)                                          \
                                  do{                                       \
                                      (__HANDLE__)->Lock = HAL_UNLOCKED;    \
                                    }while (0U)

代码地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1033481.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

stm32之串口/蓝牙控制led灯

该文章记录学习stm32串口遇到的一些问题&#xff0c;完整代码地址。 一、项目描述 通过串口或蓝牙发送指令来控制led灯。 open ------> led 亮close ------> led 灭其它 -------> 反馈给串口或蓝牙错误指令 二、项目用到的模块 stm32 串口1,PA9(TX), PA10(RX)HC…

计算机组成与设计硬软件接口学习2

并行处理器&#xff1a;从客户端到云 任务级并行或进程级并行&#xff1a;通过同时运行独立的多个程序来使用多处理器 并行处理程序&#xff1a;同时在多个处理器上运行的单个程序 通过增加硬件的方式&#xff0c;将取指令和指令译码实现并行&#xff0c;一次性取出多条指令…

MQTT 协议概要

01 MQTT协议 MQTT&#xff08;消息队列遥测传输&#xff09; 是基于 TCP/IP 协议栈而构建的支持在各方之间异步通信的消息协议。MQTT在空间和时间上将消息发送者与接收者分离&#xff0c;因此可以在不可靠的网络环境中进行扩展。虽然叫做消息队列遥测传输&#xff0c;但它与消息…

[RF学习记录][ssh library][execute Command】关键字的返回值

有时候需要判断通过ssh在远程机器上执行的命令是否正常&#xff0c;使用关键字Execute Command可以在远程机器上运行命令&#xff0c;但是默认不加任何参数的话&#xff0c;没有看到范返回值&#xff0c;而这个关键字是带了几个参数的&#xff0c;简单的试验了下这几个参数&…

点云从入门到精通技术详解100篇-单期点云的高斯曲率定位桥梁潜在损伤技术研究

目录 前言 国内外研究现状 三维激光扫描对桥梁损伤检测的研究现状 基于点云高斯曲率损伤检测的研究现状 柱体偏差检测技术研究现状 存在的问题 法向量约束高斯曲率的 TLS 桥面潜在损伤区域探测 2.1 高斯曲率探伤的基本理论 2.2 点云拓扑关系建立的方法比较 2.2.1 KD-…

机器学习第十三课--主成分分析PCA

一.高维数据 除了图片、文本数据&#xff0c;我们在实际工作中也会面临更多高维的数据。比如在评分卡模型构建过程中&#xff0c;我们通常会试着衍生出很多的特征&#xff0c;最后就得到上千维、甚至上完维特征;在广告点击率预测应用中&#xff0c;拥有几个亿特征也是常见的事…

【数学建模】2023华为杯研究生数学建模F题思路详解

强对流降水临近预报 我国地域辽阔,自然条件复杂,因此灾害性天气种类繁多,地区差异大。其中,雷雨大风、冰雹、龙卷、短时强降水等强对流天气是造成经济损失、危害生命安全最严重的一类灾害性天气[1]。以2022年为例,我国强对流天气引发风雹灾害造成的死亡失踪人数和直接经济…

git:一、GIT介绍+安装+全局配置+基础操作

版本管理系统&#xff08;SVN和Git&#xff09;&#xff1a; 集中式版本控制系统&#xff08;SVN&#xff09; SVN是集中式版本控制系统&#xff0c;版本库是集中放在中央服务器的. 工作流程如下: 1.从中央服务器远程仓库下载代码 2.修改后将代码提交到中央服务器远程仓库…

基于微信小程序的电影院订票系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境说明用户微信小程序端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文参考源码获取 前言 &#x1f497;博主介绍&…

原生js的animate()方法详解

1.介绍 Element 接口的 animate() 方法是创建一个新的 Animation 的便捷方法&#xff0c;将它应用于元素&#xff0c;然后运行动画。它将返回一个新建的 Animation 对象实例。 同时通过Element.getAnimations() 方法可获取元素所有的Animation实例。 2.语法 Element.animate…

读高性能MySQL(第4版)笔记14_备份与恢复(中)

1. 在线备份 2. 离线备份 2.1. 关闭MySQL做备份是最简单、最安全的 2.2. 所有获取一致性副本的方法中最好的 2.3. 损坏或不一致的风险最小 2.4. 根本不用关心InnoDB缓冲池中的脏页或其他缓存 2.5. 不需要担心数据在尝试备份的过程中被修改 2.5.1. 服务器不对应用提供访问…

Redis淘汰策略-架构案例2020(三十六)

上篇案例回顾&#xff1a; 解释器&#xff0c;管道过滤&#xff0c;隐式调用优缺点&#xff1f; 解释器 则是独立的语法规则&#xff0c;可以通过解释器来解析&#xff0c;可扩展性很高&#xff0c;灵活性强。 管道过滤则是侧重于数据的输入和输出&#xff0c;上一个模块的数…

stm32之看门狗

STM32 有两个看门狗&#xff0c;独立看门狗和窗口看门狗&#xff0c;独立看门狗又称宠物狗&#xff0c;窗 口看门狗又称警犬。可用来检测和解决由软件错误引起的故障。两个看门狗的原理都是当计数器达到给定的超时值时&#xff0c;产生系统复位&#xff0c;对于窗口型看门狗同…

2023.9.20 简单了解 HTTP协议 及 Fiddle 安装使用

目录 HTTP 协议基本概念 Fiddle 下载 HTTP 请求格式 HTTP 响应格式 HTTP 协议基本概念 应用层使用最广泛的协议浏览器 基于 HTTP协议 获取网站是 浏览器 和 服务器 之间的交互桥梁HTTP协议 基于传输层的 TCP协议 实现HTTP 全称为 HyperText Transfer Protocol&#xff0c;中…

Java IO流实现文件复制

目录 前言 文件复制底层逻辑 代码实现 ​编辑 重点&#xff01;&#xff01;&#xff01; 完整代码 改善思考 前言 Windows文件复制时我们是使用Ctrl C复制Ctrl V粘贴&#xff0c;上一篇文章Java基础入门对存储文件的相关操作 我们学习了Java IO流对文件的读写操作&…

数据结构--排序(1)

文章目录 排序概念直接插入排序希尔排序冒泡排序堆排序选择排序验证不同排序的运行时间 排序概念 排序指的是通过某一特征关键字&#xff08;如信息量大小&#xff0c;首字母等&#xff09;来对一连串的数据进行重新排列的操作&#xff0c;实现递增或者递减的数据排序。 稳定…

深度学习自学笔记二:逻辑回归和梯度下降法

目录 一、逻辑回归 二、逻辑回归的代价函数 三、梯度下降法 一、逻辑回归 逻辑回归是一种常用的二分类算法&#xff0c;用于将输入数据映射到一个概率输出&#xff0c;表示为属于某个类别的概率。它基于线性回归模型&#xff0c;并使用了sigmoid函数作为激活函数。 假设我们…

Git_06_创建分支/查看分支

创建分支 # 创建分支的同时&#xff0c;切换到该分支上 > git checkout -b 分支名称 # > git push origin 分支名称查看分支 # 查看本地分支 > git branch # 查看远程分支 > git branch -r # 查看所有分支 > git branch -a删除分支 # 删除本地分支 > git …

app一键加固加签名脚本 百度加固 window版本

echo off setlocal enabledelayedexpansionset KEYSTORE_PATH*.jks set KEYSTORE_PASSWORD* set KEY_ALIAS* set KEY_PASSWORD*set OUTPUT_DIR%cd%\out set UNSIGNED_DIR%cd%\unsignedREM 设置加固工具的路径和密钥 set APKPROTECT_PATH"apkprotect" set AKEY替换成你…

Postman 全局配置接口路径变量等

Postman 全局配置接口路径变量等 一、简介 这里主要是介绍通过配置postman接口测试工具&#xff0c;简化每次新增模块等接口时修改url的繁琐过程&#xff0c;方便以后查阅&#xff01;&#xff01;&#xff01; 二、全局变量设置 1、新增测试环境 新增测试环境 2、接口集合设…