草庐IT

嵌入式系统开发13——STM32输出PWM实现呼吸灯

6¿6 2023-04-12 原文

本文主要介绍在STM32F103C8T6上,利用定时器输出PWM波形,进而驱动LED实现呼吸灯。

目录

一、任务要求

使用TIM3和TIM4,分别输出一个PWM波形,PWM的占空比随时间变化,去驱动你外接的一个LED以及最小开发板上已焊接的LED(固定接在 PC13 GPIO端口),实现2个 LED呼吸灯的效果。

二、PWM简介

1、什么是PWM

脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。

通俗讲,PWM是一种 对模拟信号电平进行数字编码 的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码

PWM信号仍然是数字的 ,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用PWM进行编码。

2、相关概念

  • pwm的频率:
    是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);也就是说一秒钟PWM有多少个周期
    单位: Hz
    表示方式: 50Hz 100Hz

  • pwm的周期:
    周期: 一个脉冲信号的时间
    1s内测周期次数等于频率:T=1/f
    如:50Hz = 20ms 一个周期,频率为50Hz ,也就是说一个周期是20ms 那么一秒钟就有 50次PWM周期

  • 占空比:
    是一个脉冲周期内,高电平的时间与整个周期时间的比例
    单位: % (0%-100%)
    表示方式:20%

  • 脉宽时间: 高电平时间
    脉宽时间占总周期时间的比例,就是占空比

3、PWM的产生

通过STM32控制板,有两种方式能产生PWM,第一是利用 普通IO口 输出PWM,第二种是利用 定时器的PWM的IO口复用IO口

(1)PWM端口
STM32 的定时器除了 TIM6TIM7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出。

(2)普通IO口
一般能够输出PWM的端口都会在主要功能那一栏出现CHx的标志,而普通定时器没有出现这种标志。如图所示,上面的红框就是普通的定时器,不是专用的PWM端口。

STM32F103C8T6的PWM口
由于本人的实验环境是在stm32最小系统上实现的,因此在此给出 STM32F103C8T6 的PWM口配置。不是所有的芯片都有重映像功能的STM32F103C8T6 的四个定时器就不需要重映像。

TIM1_CH1->PA8;
TIM1_CH2->PA9;
TIM1_CH3->PA10;
TIM1_CH4->PA11;

TIM2_CH1->PA0;
TIM2_CH2->PA1;
TIM2_CH3->PA2;
TIM2_CH4->PA3;

TIM3_CH1->PA6;
TIM3_CH2->PA7;
TIM3_CH3->PB0;
TIM3_CH4->PB1;

TIM4_CH1->PB6;
TIM4_CH1->PB7;
TIM4_CH1->PB8;
TIM4_CH1->PB9;

普通IO口产生PWM
普通IO口产生一个pwm其实就是通过一个高低电平周期性的变化。确定频率就可以确定周期(T=1/f)也就是在一个周期内产生pwm的时间。
改变占空比: 确定了时间,高电平的时间不就是想要的占空比么,比如要产生一个频率1khz,占空比为70%的pwm,根据频率我们知道了周期为1ms,产生一个占空比为70%的不就是0.7ms的时间给高电平么。我们用定时器中断的方式,使0.1ms产生一次中断,计数中断次数,中断处理函数前七次中断都给高电平就可以产生对应的波形了。

区别
1)一般而言,尽量选用PWM口进行PWM输出,因为普通IO口模拟PWM的输出频率越高,进入定时器中断的次数就越快,中断间隔的时间越短,如果再有其他类型的中断也要处理时,会因为中断的优先级嵌套等待响应,影响控制精度,PWM输出误差增大,也会影响其他如ADC等中断处理,甚至会较出现单片机逻辑出错,死机或者跑飞的情况。

2)普通IO也可以输出PWM,只是产生PWM一般用转用芯片(开关电源上用的较多)或者单片机的PWM内置模块如定时器,很小直接用MCU的IO口线直接输出因为那样太耗MCU资源了。

4、PWM的通道

每一个捕获/比较通道都是围绕着一个捕获/比较寄存器(包含影子寄存器),包括捕获的输入部分(数字滤波、多路复用和预分频器),和输出部分(比较器和输出控制)

捕获/比较模块由一个预装载寄存器和一个影子寄存器组成。读写过程仅操作预装载寄存器

在捕获模式下,捕获发生在影子寄存器上,然后再复制到预装载寄存器中

在比较模式下,预装载寄存器的内容被复制到影子寄存器中,然后影子寄存器的内容和计数器进行比较

5、PWM工作过程


每个定时器有四个通道,每一个通道都有一个捕获比较寄存器,将寄存器值和计数器值比较,通过比较结果输出高低电平,实现输出PWM信号

如图为向上计数:
定时器重装载值为ARR,比较值CCRx
t时刻对计数器值和比较值进行比较
如果计数器值小于CCRx值,输出低电平
如果计数器值大于CCRx值,输出高电平

PWM的一个周期
定时器从0开始向上计数
当0-t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平
t1-t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平
当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数…循环此过程
至此一个PWM周期完成

影响因素
ARR : 决定PWM周期(在时钟频率一定的情况下,当前为默认内部时钟CK_INT)
CCRx : 决定PWM占空比(高低电平所占整个周期比例)

PWM工作过程(以通道1为例)

  • TIMx_CCMR1 寄存器的 OC1M[2:0] 位,设置输出模式控制器
    110: PWM模式1
    111: PWM模式2

  • 计数器值 TIMx_CNT 与通道1捕获比较寄存器 CCR1 进行比较,通过比较结果输出有效电平和无效电平
    OC1REF =0 无效电平
    OC1REF =1 无效电平

  • 通过输出模式控制器产生的信号
    TIMx_CCER 寄存器的 CC1P 位,设置输入/捕获通道1输出极性
    0: 高电平有效
    1: 低电平有效

  • TIMx_CCER:CC1E 位控制输出使能电路,信号由此输出到对应引脚
    0: 关闭
    1: 开启

6、PWM输出高低电平

计数器值 TIMx_CNT 与捕获比较寄存器值 CCRx 比较后,最终输出高电平还是低电平, 由TIMx_CCMR1:OC1M 位和 TIMx_CCER:CC1P 位共同决定

(1)TIMx_CCMR1 寄存器的 OC1M[2:0] 位,设置PWM 模式1模式2

通过设置模式1或模式2,决定了比较结果输出有效或无效电平

(2)TIMx_CCER 寄存器的 CC1P 位,设置输入/捕获通道1输出极性

通过设置输出极性,确定有效或无效电平为最终输出的高电平或低电平

总结:
模式1:
CNT<CCR 为有效电平 //(OC1REF = 1)
CNT>CCR 为无效电平 //(OC1REF = 0)
模式2:
CNT<CCR 为无效电平 //(OC1REF = 0)
CNT>CCR 为有效电平 //(OC1REF = 1)
CC1P:
0:高电平有效
1:低电平有效

目录

7、PWM的计数模式

向上计数模式:
下面是一个PWM模式1的例子。当 TIMx_CNT<TIMx_CCRx 时PWM信号参考 OCxREF 为高,否则为低。如果 TIMx_CCRx 中的比较值大于自动重装载值 (TIMx_ARR) ,则 OCxREF 保持为’1’。如果比较值为0,则 OCxREF 保持为’0’:

向下计数模式:
在PWM模式1,当 TIMx_CNT>TIMx_CCRx 时参考信号 OCxREF 为低,否则为高。如果 TIMx_CCRx 中的比较值大于 TIMx_ARR 中的自动重装载值,则 OCxREF 保持为’1’。该模式下不能产生0%的PWM波形

中央对齐模式:
TIMx_CR1 寄存器中的 CMS 位不为’00’时,为中央对齐模式(所有其他的配置对 OCxREF/OCx 信号都有相同的作用)。根据不同的 CMS 位设置,比较标志可以在计数器向上计数时被置’1’、在计数器向下计数时被置’1’、或在计数器向上和向下计数时被置’1’。TIMx_CR1 寄存器中的计数方向位(DIR)由硬件更新,不要用软件修改它。

8、PWM相关寄存器

包含三个寄存器: 捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4) 。设置 TIMx_CCMRx 寄存器 OCxPE 位以使能相应的预装载寄存器,最后还要设置 TIMx_CR1 寄存器的 ARPE 位,(在向上计数或中心对称模式中)使能自动重装载的预装载寄存器。其中模式设置位 OCxM 位,此位由3位组成,一共可以配置成7种模式,我们使用的是PWM模式,所以 这三位必须为110/111

捕获/比较模式寄存器总共2个,TIMx_CCMR1TIMx_CCMR2TIMx_CCMR1 控制CH1和CH2,TIMx_CCMR2 控制CH3和CH4。

下面来简单介绍一下这三种寄存器:

  • 捕获/比较模式寄存器(TIMx_CCMR1)
    作用:在PWM输出模式下,确定PWM的模式、使能相应的预装载寄存器等操作:

  • 捕获/比较使能寄存器(TIMx_CCER)
    作用:在PWM输出模式下,确定PWM的输出极性和输出使能:

  • 捕获/比较寄存器(TIMx_CCR1)
    作用:在PWM输出模式下,确定比较的值:

三、创建工程

(1)打开 STM32CubeMX,点击 ACCEE TO MCU SELECTOR

(2)在搜索框输入 STM32F103C8,双击选择 STM32F103C8Tx

(3)选择调试接口,点击 System Core,选择 SYS,在右侧弹出的菜单栏中选 Serial Wire

(4)打开外部时钟,点击 System Core,选择 RCC,在右侧弹出的菜单栏中选择Crystal/Ceramic Resonator

(5)配置 TIM3,勾选 Internal Clock(内部时钟);将 Channel1 设置为 PWM Generation CH1(PWM输出通道1);
Prtscaler (定时器分频系数) 设置为71,即72分频——1MHz;Counter Mode(计数模式)设置为Up(向上计数模式);Counter Period(自动重装载值) 设置为50000,计数器从0向上计数(递增)到自动装载值,然后再次回到0开始计数,并产生一个计数溢出事件,即0.05s;CKD(时钟分频因子) 设置为No Division (不分频 )

(6)将 TIM4 设置的和 TIM3 一样

(7)配置时钟,将 HCLK 设置为 72MHz

(8)设置项目的名称、位置和编译环境,生成项目

(9)打开项目,准备代码编辑

四、代码编写

主要利用下面两个函数来控制定时器实现PWM输出

//开启 TIMx 的通道x,输出PWM
void HAL_TIM_PWM_Start(&htimx, TIM_CHANNEL_x);
/*
 * @Descript	PWM占空比更改
 * @param		htimx			相关定时器指针	
 * @param		TIM_CHANNEL_x	相关PWM通道
 * @param		Pulse			Duty_cycle = Pulse / Counter Period 
 * @return		void
*/
//修改TIMx的通道x的PWM波形的占空比
void __HAL_TIM_SET_COMPARE(&htimx, TIM_CHANNEL_x, Pulse);
/*
 * @Descript	PWM频率更改
 * @param		htimx			相关定时器指针	
 * @param		Counter_Period	PWM_fre = TIM_frq / ( Prescaler + 1 ) / ( Counter Period + 1 )
 * @return		void
*/

(1)在 main.c 文件中定义一个变量来记录 pwm 波形的占空比

uint16_t pwm=0;   //占空比

(2)打开定时器 TIM3TIM4PWM 通道1

	HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); 
	HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1); 

(3)在主函数 while 循环里加入如下代码

	while (pwm< 500)                                       //在0.5s的时间内逐渐增大输出PWM的占空比,即让LED慢慢变亮
	{
		pwm++;
		__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, pwm);    //修改TIM3的占空比
		__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_1, pwm);  	//修改TIM4的占空比		
		HAL_Delay(1);
	}
	while (pwm>=0)                                        //在0.5s的时间内逐渐减小输出PWM的占空比,即让LED慢慢变暗
	{
		pwm--;
		__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, pwm);   //修改TIM3的占空比 
		__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_1, pwm);    //修改TIM4的占空比
		HAL_Delay(1);
	}
	HAL_Delay(100);                                         //延时0.1s

五、编译

(1)点击 Options for Target…,在 Output 下勾选 Create HEX File

(2)在 Debug 下勾选 Use Simulator,将 Dialog DLL下的输入框改为 DARMSTM.DLL,Parameter 输入框改为 -pSTM32F103C8

(3)在 Target 选择使用 V5编译器

(4)点击 Rebuild进行编译

六、烧录

1、硬件连接

USB转TTLSTM32F103C8T6
GNDG
3V33V3
RXDPA9
TXDPA10

注意将核心板上的BOOT0设置为1,BOOT1设置为0


LED模块连接方法:

输出端口目的端口
3.3VLED阳极
PA6LED阴极
PB6PC13

其中的PB6->PC13是为了将TIM4_CH1的输出PWM波形接到PC13上,PC13连接着板子上的LED,从而驱动板子上的LED实现呼吸灯

2、烧录

打开 FlyMcu 烧录助手,选择刚刚生成的 HEX文件,点击 开始编程 进行烧录

3、运行效果

PWM呼吸灯

七、总结

本文主要介绍PWM相关理论知识,在学习理论知识的基础上,实现在STM32F103C8T6上,利用定时器TIM3和TIM4输出PWM波形,PWM的占空比随时间变化,去驱动你外接的一个LED以及最小开发板上已焊接的LED(固定接在 PC13 GPIO端口),实现2个 LED呼吸灯的效果。通过本次的练习,让我更加深入的理解了STM32的定时器功能以及PWM的相关知识。

学习要把理论和实践结合起来。只有在实践过程中一步一步的试错,在“发现问题——分析问题——解决问题”的过程中,逐步加深自己对理论和知识的理解。就比如在本次实践过程中,如果仅仅只阅读一些理论知识,那么对定时器和PWM的理解就会大打折扣。并且在实现呼吸灯的过程中,我也遇到了不少问题,如果没有进行实践,那将很难发现这些问题,也将不会知道如何实现PWM输出和定时器的操作。

最后就是当我们碰到解决不了的问题时,要多查阅资料,多参考他人的成功方法。这里不是指去抄别人的代码,而是去理解大佬们解决问题的思路,学习大佬们分析问题的方法以及严谨的思维方式,在理解思路的过程中提升自我。

感谢大家的阅读,欢迎大家指出本文存在的问题!


参考列表:
1.STM32CUBEMX_基于PWM的呼吸灯
2.stm32f103呼吸灯(PWM脉冲宽度调制)
3.使用STM32输出PWM波形
4.STM32 PWM基本知识及配置过程

有关嵌入式系统开发13——STM32输出PWM实现呼吸灯的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  3. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  4. ruby - 安装libv8(3.11.8.13)出错,Bundler无法继续 - 2

    运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin

  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. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  7. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  8. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  9. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

  10. ruby-on-rails - gem install rmagick -v 2.13.1 错误 Failed to build gem native extension on Mac OS 10.9.1 - 2

    我已经通过提供MagickWand.h的路径尝试了一切,我安装了命令工具。谁能帮帮我?$geminstallrmagick-v2.13.1Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingrmagick:ERROR:Failedtobuildgemnativeextension./Users/ghazanfarali/.rvm/rubies/ruby-1.8.7-p357/bin/rubyextconf.rbcheckingforRubyversion>=1.8.5...yescheckingfor/

随机推荐