草庐IT

STM32定时器学习-PWM输出

来人皆可得 2023-04-13 原文

基本定时器

最基础功能定时,两个基础定时器TM6和TM7,基本定时器时钟源只来自内部时钟。
如果我们想要一个1s的定时,那么我们应该怎么设置定时器呢?

首先了解定时器的基本初始化结构体

 typedef struct {
     uint16_t Prescaler;         // 预分频器
     uint16_t CounterMode;       // 计数模式
     uint32_t Period;            // 定时器周期
     uint16_t ClockDivision;     // 时钟分频
     uint8_t RepetitionCounter;  // 重复计算器
 } TIM_Base_InitTypeDef;
  1. 首先考虑计数模式(CounterMode),定时器有五种计数模式,向上计数、向下计数、三种中心对齐模式,本实验的基本定时器只能是向上计数,从0开始计数,并且无需初始化。
  2. 定时器预分频器设置(Prescaler),设置它来取得每次计数的时间。例如每次计数的时间为100us,我们称之为时钟源周期,因为输入时钟为84MHZ,所以设置预分频器为(8400-1)
  3. 定时器周期(Period) 刚刚我们知道了时钟源时间,一个时钟源时间我们刚刚设定为100us,如果我们有10000个这样的时间我们就有了1s定时。而系统是从0开始计数的所以我们设定时需要减一(10000-1)。
  4. ClockDivision:时钟分频,设置定时器时钟 CK_INT 频率与数字滤波器采样时钟频率分频比, 基本定时器没有此功能,不用设置
  5. RepetitionCounter:重复计数器,属于高级控制寄存器专用寄存器位,利用它可以非常容易控制输出 PWM 的个数。 这里不用设置

基本定时器只需要设定两个成员就行。
我们来看初始化基本定时器函数

 static void TIM_Mode_Config(void)
 {
     // 开启 TIMx_CLK,x[6,7]
     __TIM6_CLK_ENABLE();

     TIM_TimeBaseStructure.Instance = TIM6;
     /* 累计 TIM_Period 个后产生一个更新或者中断*/
     //当定时器从 0 计数到 4999,即为 5000 次,为一个定时周期
     TIM_TimeBaseStructure.Init.Period = 5000-1;

     //定时器时钟源 TIMxCLK = 2 * PCLK1
     // PCLK1 = HCLK / 4
     // => TIMxCLK=HCLK/2=SystemCoreClock/2=84MHz
     // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10000Hz
     TIM_TimeBaseStructure.Init.Prescaler = 8400-1;

     // 初始化定时器 TIMx, x[2,3,4,5]
     HAL_TIM_Base_Init(&TIM_TimeBaseStructure);

     // 开启定时器更新中断
     HAL_TIM_Base_Start_IT(&TIM_TimeBaseStructure);
 }

最后使用 HAL_TIM_Base_Start_IT 函数开启定时器和更新中断。
主函数保持初始化就好了,之后我们设置了中断,在stm32f4xx_it.c的函数中的中断服务函数和中断回调函数。
我来解释一下,这里中断服务函数就是当定时器按照定时时间计数完成后程序进入中断服务函数进行程序运行,而这里我们通过HAL_TIM_IRQHandler调用中断回调函数,之后系统将变成双线程,一边返回原来计数继续计数,一边就行回调函数的运行。如果不用中断服务函数可能会影响定时器精准,这是我自己的理解,如果不对的话请大佬帮我指出

void TIM6_DAC_IRQn (void)
 {
     HAL_TIM_IRQHandler(&TIM_TimeBaseStructure);
 }

 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
 {
     if (htim==(&TIM_TimeBaseStructure)) {
     //添加你自己的函数
     }
 }

一个最基本的基本定时器就完成了。

TIM-高级定时器

TIM1\TIM8是高级定时器,其他为通用定时器,高级定时器包括了通用定时器的功能。
通用定时器功能比基本定时器多了:输入捕获和输出比较功能
而高级定时器又比通用定时器多了可编程死区互补输出、重复计数器、带刹车(断路)功能
我现在也不理解那功能框图有什么用,只能让我更迷惑,等着我以后会了解了再补充。
解释一下各个功能,以后用到再学:

  1. 输入捕获 输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,通常用于测量输入信号的脉宽、测量 PWM 输入信号的频率及占空比。

  2. 输出比较 定时器通过对预设的比较值与计数器的值做匹配比较之后,并依据相应的输出模式从而实现各类输出。如PWM输出、电平翻转、单脉冲输出、强制输出等。我接下来可能要做电机系统,所以应该会用到PWM输出,在下面着重讲一下。

  3. 可编程死区互补输出 死区,简单解释:通常,大功率电机、变频器等,末端都是由大功率管、IGBT等元件组成的H桥或3相桥。每个桥的上半桥和下半桥是是绝对不能同时导通的,但高速的PWM驱动信号在达到功率元件的控制极时,往往会由于各种各样的原因产生延迟的效果,造成某个半桥元件在应该关断时没有关断,造成功率元件烧毁。死区就是在上半桥关断后,延迟一段时间再打开下半桥或在下半桥关断后,延迟一段时间再打开上半桥,从而避免功率元件烧毁。这段延迟时间就是死区。解释来源:
    https://blog.csdn.net/u011456016/article/details/70238923

  4. 重复计数器 其中重复计数器意思指的是对计数器溢出(包括上溢和下溢)的次数进行计数,是递减的,每到达0时才会产生一个更新事件。而TIM1_RCR就是重复计数器减为0时重新从TIM1_RCR(重复计数器寄存器)的值开始计数.
    解释来源

  5. 带刹车(断路)功能 没有找到具体描述,但是有很通俗的解释,就是紧急刹车停止pwm波。

PWM波输出实验

PWM,英文名Pulse Width Modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码。

其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达4路的 PWM 输出,这样, STM32 最多可以同时产生 30 路 PWM 输出! 但是!!!同一个定时器TIM只能产生一个频率的PWM波,你只能改变占空比。
接下来是怎么用stm32产生我们想要的pwm波。

1.初始化定时器部分

static void TIM_Mode_Config(void)
{
	TIM_OC_InitTypeDef  TIM_OCInitStructure;
  
	/*使能定时器*/
	__HAL_RCC_TIM8_CLK_ENABLE();

	TIM_AdvanceHandle.Instance = TIM8;    
	/* 累计 TIM_Period个后产生一个更新或者中断 */		
	//当定时器从0计数到TIM_PERIOD,即为TIM_PERIOD次,为一个定时周期
	TIM_AdvanceHandle.Init.Period =  0xFFFF; 
	// 高级控制定时器时钟源TIMxCLK = HCLK=168MHz 
	// 设定定时器频率为=TIMxCLK/(TIM_PRESCALER-1)
	TIM_TimeBaseStructure.Init.Period = 1000-1;
	TIM_AdvanceHandle.Init.Prescaler = 168-1;
	/* 计数方式 */
	TIM_AdvanceHandle.Init.CounterMode = TIM_COUNTERMODE_UP;            
	/* 采样时钟分频,不分频 */	
	TIM_AdvanceHandle.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;   
   	/* 定时器一次一中断 */
	TIM_AdvanceHandle.Init.RepetitionCounter = 0 ;  		
	/*初始化定时器*/
  HAL_TIM_Base_Init(&TIM_TimeBaseStructure);
  
	/*PWM模式配置*/
  TIM_OCInitStructure.OCMode = TIM_OCMODE_PWM1;//配置为PWM模式1
  TIM_OCInitStructure.Pulse = 500-1 ;//默认占空比为50%
  TIM_OCInitStructure.OCFastMode = TIM_OCFAST_DISABLE;
	/*当定时器计数值小于CCR1_Val时为高电平*/
  TIM_OCInitStructure.OCPolarity = TIM_OCPOLARITY_HIGH;	
	
	/*配置PWM通道*/
  HAL_TIM_PWM_ConfigChannel(&TIM_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_1);
	/*开始输出PWM*/
	HAL_TIM_PWM_Start(&TIM_TimeBaseStructure,TIM_CHANNEL_1);
	
	/*配置脉宽*/
  TIM_OCInitStructure.Pulse = tim_per/2;//默认占空比为50%
	/*配置PWM通道*/
  HAL_TIM_PWM_ConfigChannel(&TIM_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_2);
	/*开始输出PWM*/
	HAL_TIM_PWM_Start(&TIM_TimeBaseStructure,TIM_CHANNEL_2);
	

  1. 了解输出比较结构体
    输出比较结构体TIM_OCInitTypeDef用于输出比较模式,与TIM_OCx_SetConfig函数配合使用完成指定定时器输出通道初始化配置。 高级控制定时器有四个定时器通道,使用时都必须单独设置。
typedef struct {
 uint32_t OCMode; // 比较输出模式
 uint32_t Pulse; // 脉冲宽度
 uint32_t OCPolarity; // 输出极性
 uint32_t OCNPolarity; // 互补输出极性
 uint32_t OCFastMode; // 比较输出模式快速使能
 uint32_t OCIdleState; // 空闲状态下比较输出状态
 uint32_t OCNIdleState; // 空闲状态下比较互补输出状态
 }TIM_OCInitTypeDef;

OCMode:比较输出模式选择,总共有八种,常用的为PWM1/PWM2。它设定CCMRx寄存器OCxM[2:0]位的值。在 PWM 模式( 1 或 2)下, TIMx_CNT 总是与 TIMx_CCRx 进行比较,以确定是TIMx_CCRx大于 TIMx_CNT 还是 TIMx_CNT小于TIMx_CCRx(取决于计数器计数方向)。

Pulse:比较输出脉冲宽度,实际设定比较寄存器CCR的值,决定脉冲宽度。可设置范围为0至65535。这个可以设置占空比,取决于你在前面的定时器预分频器设置定时器周期在407中,我们将预分频数设置为(168-1)得到1us的时钟源周期,之后将定时器周期设置为(1000-1)得到1ms的定时器周期,我们将pulse设置为(500-1)得到百分之50的占空比。

OCPolarity:比较输出极性,可选OCx为高电平有效或低电平有效。它决定着定时器通道有效电平。它设定CCER寄存器的CCxP位的值。一般设置为高电平有效。他与下面的OCNPolarity都为高电平或者都为低电平有效时输出高电平,否则为低电平。

OCNPolarity:比较互补输出极性,可选OCxN为高电平有效或低电平有效。它设定TIMx_CCER寄存器的CCxNP位的值。

OCFastMode:比较输出模式快速使能。它设定TIMx_CCMR寄存器的,OCxFE位的值可以快速使能或者禁能输出。开关作用吧。

OCIdleState:空闲状态时通道输出电平设置,可选输出1或输出0,即在空闲状态(BDTR_MOE位为0)时, 经过死区时间后定时器通道输出高电平或低电平。它设定CR2寄存器的OISx位的值。死区时间过后设置要输出高的还是输出低的,大概是这样,暂时用不到,用到的再说。

其他都是引脚初始化函数。
下面介绍设置通道占空比函数

__HAL_TIM_SET_COMPARE()

根据预分频数的大小我们来设置占空比,例如,预分频数(1000-1)我们设置占空比,100,就是百分之10的占空比。
接下来是必要的GPIO口的初始化

static void TIMx_GPIO_Config(void) 
{
 GPIO_InitTypeDef GPIO_InitStruct;
  
  /* 定时器通道功能引脚端口时钟使能 */
	
	__HAL_RCC_GPIOA_CLK_ENABLE();
	__HAL_RCC_GPIOB_CLK_ENABLE();
  
  /* 定时器通道1功能引脚IO初始化 */
	/*设置输出类型*/
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	/*设置引脚速率 */ 
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	/*设置复用*/
  GPIO_InitStruct.Alternate = GENERAL_TIM_GPIO_AF;
	
	/*选择要控制的GPIO引脚*/	
	GPIO_InitStruct.Pin = GENERAL_TIM_CH1_PIN;
	/*调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO*/
  HAL_GPIO_Init(GENERAL_TIM_CH1_GPIO_PORT, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GENERAL_TIM_CH2_PIN;	
  HAL_GPIO_Init(GENERAL_TIM_CH2_GPIO_PORT, &GPIO_InitStruct);
	
}
/**
  * @brief  初始化控制通用定时器
  * @param  无
  * @retval 无
  */
void TIMx_Configuration(void)
{
	TIMx_GPIO_Config();
  
  TIM_PWMOUTPUT_Config();
}

我们之后就可以用改变占空比的函数进行修改,这个是通用定时器输出pwm波,很简单,难的是高级定时器的设置,涉及到很多模式,本实验先到这里。

22/03/07更新

PWM互补输出和输出比较吗模式区别:
PWM模式: ARR设置频率,CCR设置占空比,频率和占空比可以任意设置,起始相位不能设置。

输出比较模式:ARR设置频率,CCR设置相位,频率和起始相位可以任意设置,占空比不能设置。输出频率为理论计算值一半。
https://blog.csdn.net/qq_20222919/article/details/106564957

有关STM32定时器学习-PWM输出的更多相关文章

  1. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  2. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  3. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  4. ruby - 将 spawn() 的标准输出/标准错误重定向到 Ruby 中的字符串 - 2

    我想使用spawn(针对多个并发子进程)在Ruby中执行一个外部进程,并将标准输出或标准错误收集到一个字符串中,其方式类似于使用Python的子进程Popen.communicate()可以完成的操作。我尝试将:out/:err重定向到一个新的StringIO对象,但这会生成一个ArgumentError,并且临时重新定义$stdxxx会混淆子进程的输出。 最佳答案 如果你不喜欢popen,这是我的方法:r,w=IO.pipepid=Process.spawn(command,:out=>w,:err=>[:child,:out])

  5. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  6. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  7. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  8. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  9. ruby - Ruby 是否使用 $stdout 来写入 puts 和 return 的输出? - 2

    我想知道Ruby用来在命令行打印这些东西的输出流:irb(main):001:0>a="test"=>"test"irb(main):002:0>putsatest=>nilirb(main):003:0>a=>"test"$stdout是否用于irb(main):002:0>和irb(main):003:0>?而且,在这两次调用之间,$stdout的值是否有任何变化?另外,有人能告诉我打印/写入这些内容的Ruby源代码吗? 最佳答案 是的。而且很容易向自己测试/证明。在命令行试试这个:ruby-e'puts"foo"'>test.

  10. ruby-on-rails - 无法在 Rails 助手中捕获 block 的输出 - 2

    我在使用自定义RailsFormBuilder时遇到了问题,从昨天晚上开始我就发疯了。基本上我想对我的构建器方法之一有一个可选block,以便我可以在我的主要content_tag中显示其他内容。:defform_field(method,&block)content_tag(:div,class:'field')doconcatlabel(method,"Label#{method}")concattext_field(method)capture(&block)ifblock_given?endend当我在我的一个Slim模板中调用该方法时,如下所示:=f.form_field:e

随机推荐