当前位置: 首页 > news >正文

初识STM32单片机-TIM定时器

初识STM32单片机-TIM定时器

  • 一、定时器概述
  • 二、定时器类型
    • 2.1 基本定时器(TIM6和TIM7)
    • 2.2 通用定时器(TIM2、TIM3、TIM4和TIM5)
    • 2.3 高级定时器(TIM1和TIM8)
  • 三、定时中断基本结构和时基单元工作时序
    • 3.1 定时器基本结构
    • 3.2 预分频器时序
    • 3.3 计数器时序
      • 3.3.1 计数器有无预装时序(有无缓冲寄存器)
  • 四、TIM输出比较OC
    • [4.1 PWM波形](https://blog.csdn.net/m0_51319492/article/details/138548314)
    • [4.2 舵机](https://blog.csdn.net/m0_51319492/article/details/138548314)
    • 4.3 直流电机
  • 五、TIM输入捕获IC
    • 5.1 频率测量
    • 5.2 输入捕获/PWMI基本结构
      • 5.2.1 输入捕获基本结构
      • 5.2.2 PWMI基本结构
  • 六、编码器
    • 6.1 正交编码器方向
    • 6.2 编码器电路和基本结构
  • 七、代码编写
    • 7.1 定时器库函数介绍
    • 7.2 定时器定时中断代码
    • 7.3 定时器外部时钟申请
    • 7.4 定时器输出比较代码
      • 7.4.1 PWM驱动LED呼吸灯
      • 7.4.2 PWM驱动舵机
      • 7.4.3 PWM驱动直流电机
    • 7.5 定时器输入捕获代码
      • 7.5.1 输入捕获模式测频率
      • 7.5.2 PWMI模式测频率和占空比
    • 7.6 编码器接口测速

一、定时器概述

  • TIM(Timer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16计数器预分频器自动重装寄存器时基单元,在72MHz计数时钟下可以实现最大59.65s的定时

  频率1MHz对应周期1/1MHz = 1us, 1KHz对应1ms, 1Hz对应1s
  在STM32中一个基准时钟(时钟周期)是1s/72MHz,计72个数字就是过了1s/72MHz×72 = 1us,如果计72000个数,那就是1ms

  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择输入捕获输出比较编码器接口主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器通用定时器基本定时器三种类型

二、定时器类型

在这里插入图片描述

  高级到低级向下兼容

  • STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4

2.1 基本定时器(TIM6和TIM7)

  基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动
  它们可以作为通用定时器提供时间基准,特别地可以为数模转换器(DAC)提供时钟。实际上,它们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC

  主要功能:

  1. 16位自动重装载累加计数器
  2. 16位可编程(可实施修改)预分频器,用于对输入的时钟按系数为1~65535之间的任意数值分频
  3. 触发DAC的同步电路
  4. 更新事件(计数器溢出)时产生中断/DMA中断

在这里插入图片描述

  RCC的TIMxCLK内部时钟输入,频率值是系统的主频72MHz,所以通向时基单元基准频率就是72MHz
  PSC预分频器CNT计数器自动重装载寄存器共同组成时基单元PSC72MHz的基准时钟进行预分频(寄存器写0就是1分频,写1就是2分频输出频率=输入频率/2=72MHz/2)。CNT(16位)对预分频后的时钟进行计数(每来一个上升沿,计数时钟加1)。自动重装载寄存器就是存的写入的计数目标,运行过程中,计数值不断自增,自动重装值固定的目标,当计数值等于自动重装值,即计数达到,产生中断信号,并清零计数器,开始下一次的计数
  其中UI更新中断,通往NVICU代表更新事件不会触发中断,触发其他事件的工作

  主从模式触发DAC:更新事件U映射TRGO,TRGO就可以触发DAC不需要利用中断去执行

  C51系列单片机是设定计数初值,STM32系列是设定计数终值

2.2 通用定时器(TIM2、TIM3、TIM4和TIM5)

  通用定时器是一个通过可编程分频器驱动16位自动装载计数器构成
  它适合于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较PWM)
  使用定时器预分频器RCC时钟控制器预分频器脉冲长度波形周期可以在几个微秒和几个毫秒间调整

  主要功能:

  1. 16位向上(计数器从0开始,向上自增,计到重装值,清零同时申请中断)、向下(计数器从重装值开始,向下自减,减到0以后,回到重装值同时申请中断)、向上/向下(计数器从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,申请中断)自动装载计数器
  2. 16位可编程(可以实现修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
  3. 4个独立通道输入捕获输出比较PWM生成单脉冲模式输出
  4. 使用外部信号控制定时器和定时器互连的同步电路
  5. 如下事件发生时产生中断/DMA
  • 更新:计数器向上溢出/向下溢出,计数器初始化(软件或者内部/外部触发)
  • 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
  • 输入捕获
  • 输出比较
  1. 支持针对定位的增量(正交)编码器和霍尔传感器电路
  2. 触发输入作为外部时钟或者按周期的电流管理

在这里插入图片描述

  时基单元的基本结构:预分频器计数器自动重装载寄存器同基本定时器,工作流程也是一样,但对于通用计数器而言,计数器的模式就不止向上计数这一种了

  下面介绍通用定时器与基本定时器不同的地方

  如下图所示是内外时钟源选择主从触发模式的结构
  基本定时器只能选择内部时钟,也就是系统频率72MHz通用定时器里,时钟源不仅可以选择72MHz时钟,也可以选择外部时钟,相当于定时器指定的外部引脚上输入一个方波信号,来提供定时器的时钟

  选择内部还是外部来提供时钟的本质区别就是计数频率是72MHz,还是外部方波信号

  TIMx_ETR引脚上的外部时钟,通过ETRP、输入滤波、ETRF进入时基单元,这一路叫做外部时钟模式2TRGI(触发输入)也可以提供时钟,由ETR、ITR(其他定时器)、CH1引脚边沿、CH1、CH2提供,可以触发定时器的从模式,叫做外部时钟模式1

在这里插入图片描述

  如下图是输出比较电路
  总共有4个通道,分别对应CH1-CH4的引脚,用于输出PWM的波形

Alt

  下面是输入捕获电路
  也是4个通道,对应的是CH1-CH4的引脚,用于测量输入方波的频率

在这里插入图片描述

  下面是捕获/比较寄存器,是输入捕获和输出比较电路公用的,输入捕获和输出比较不可以同时使用

Alt

2.3 高级定时器(TIM1和TIM8)

  高级定时器由一个16位的自动装载计数器组成,由一个可编程的预分频器驱动
  它适合多种用途,包含测量输入信号的脉冲宽度(输入捕获),或者产生输出波形(输出比较PWM、嵌入死区时间的互补PWM等)
  使用定时器预分频器和RCC时钟控制预分频器,可以实现脉冲宽度和波形周期从几个微秒到几个毫秒的调节

  主要功能:

  1. 16位向上(计数器从0开始,向上自增,计到重装值,清零同时申请中断)、向下(计数器从重装值开始,向下自减,减到0以后,回到重装值同时申请中断)、向上/向下(计数器从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,申请中断)自动装载计数器
  2. 16位可编程(可以实现修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
  3. 4个独立通道:输入捕获、输出比较、PWM生成和单脉冲模式输出
  4. 死区时间可编程的互补输出
  5. 使用外部信号控制定时器和定时器互连的同步电路
  6. 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
  7. 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
  8. 如下事件发生时产生中断/DMA
  • 更新:计数器向上溢出/向下溢出,计数器初始化(软件或者内部/外部触发)
  • 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
  • 输入捕获
  • 输出比较
  • 刹车信号输入
  1. 支持针对定位的增量(正交)编码器和霍尔传感器电路
  2. 触发输入作为外部时钟或者按周期的电流管理

  下面给出高级定时器的框图

在这里插入图片描述

  从上图中可以看出高级定时器通用定时器不同的部分仅两块内容
  第一块是申请中断的部分,加了一个重复次数计数器,可以实现每隔几个计数周期产生一次更新事件和更新中断,通用定时器是一个计数周期一个中断,高级定时器相当于对输出的更新信号又进行了一次分频
  第二块是对输出比较器的升级DTG死区生成电路,右边前三路的输出引脚由原来的一个变为了两个互补的输出,可以输出一组互补的PWM波,为了驱动三相无刷电机。下面的是刹车输入功能,为了给电机驱动提供安全保障的,如果外部引脚BKIN产生了刹车信号或者内部时钟失效,那么控制电路会自动切断电机的输出,防止意外发生

三、定时中断基本结构和时基单元工作时序

3.1 定时器基本结构

  下面给出定时中断基本结构,即定时器的工作配置流程

在这里插入图片描述

  其中最重要的就是由PSC预分频器CNT计数器ARR自动重装器组成的时基单元运行控制控制寄存器的一些位,比如启动停止向上或者向下计数等等,就可以控制时基单元的运行了。
  左半部分是为时基单元提供时钟的部分,可以选择RCC提供内部时钟,或者ETR引脚提供的外部时钟模式2,也可以选择触发输入(ETR、ITRx其他定时器、TIx捕获通道)当作外部时钟,即外部时钟模式1,或者是由编码器提供时钟。
  最右边是计时时间到达时,产生更新中断后的信号去向,其中如果是高级定时器,还会再输出信号部分多个重复计数器,中断信号会先在状态寄存器里置一个中断标志位,经过中断输出控制(判断哪个中断允许),进入NVIC,最后进入中断函数

3.2 预分频器时序

  当预分频器的参数从0变到1(1分频(不分频)和2分频)时,计数器的时序图如下所示
  CK_PSC内部时钟CNT_EN计数器使能,高电平计数器正常运行,低电平关闭;CK_CNT定时器时钟,它既是预分频器的时钟输出,也是计数器的时钟输入。前半段,预分频器系数为0(1分频),计数器的时钟等于预分频器的时钟;后半段,预分频器系数为1(2分频),计数器的时钟变为预分频器时钟的一半;在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增(到达FC后变0,ARR自动重装值就是FC)
  下面三行描述的是预分频器的一种缓冲机制,当计数计到一半的时候改变了分频值,这个变化不会立刻生效,而是会等到本次计数周期结束产生更新事件,预分频器寄存器的值才会被传递到预分频缓冲器中,才会生效

  计数频率CK_CNT = CK_PSC/(PSC+1)

在这里插入图片描述

3.3 计数器时序

  计数器时序图,内部时钟分频因子为2(2分频),即分频系数为1
  CK_INT内部时钟72MHz,CNT_EN时钟使能,CK_CNT为定时器时钟,周期为内部时钟的一半(2分频),计数器上升沿自增,自增到0036溢出;产生更新事件脉冲(UEV),置一个更新中断标志位(UIF置1申请中断,中断响应后,手动清零标志位)

  计数器溢出频率
  CK_CNT_OV
  = CK_CNT/(ARR+1)
  =CK_PSC/(PSC+1)/(ARR+1)

  其中CK_PSC是内部时钟72MHz或者外部方波信号、PSC是给定分频器的分频、ARR为自动重装值(计数目标值)
  计数器溢出时间取溢出频率的倒数即可
  其中也是带缓冲机制的,影子寄存器可以自己设置,时序如下所示

在这里插入图片描述

3.3.1 计数器有无预装时序(有无缓冲寄存器)

  当ARPE = 0时的更新事件(TIMx_ARR没有预装入)
在这里插入图片描述  计数目标突然从FF变成了36,所以直接计到36就产生更新中断,开始下一轮计数

  当ARPE = 1时的更新事件(预装入了TIMx_ARR)

在这里插入图片描述

  计数目标从F5变成了36,但是并没有立即计到36停止,而是先计到F5,产生更新中断以后,下一次计数周期的计数目标是36
  影子寄存器才是真正起作用的,影子寄存器的功能就是让值的变化和更新事件同步发生(比如计数已经到31,你突然改成重装值30),防止在运行途中更改造成错误

四、TIM输出比较OC

  • 输出比较可以通过比较CNT(计数器)和CCR(捕获/比较寄存器)值的关系,来对输出电平进行置1置0电平翻转的操作,用于输出一定频率和占空比的PWM波形

  如下图所示就是为CNT计数器CCR,这是输入捕获和输出比较公用的寄存器。当使用输入捕获时,它就是捕获寄存器;当使用输出比较时,它就是比较寄存器
  本节介绍输出比较,所以这块电路会比较CNT和CCR的值,CNT计数自增,CCR是我们给定的值

Alt

  • 每个高级定时器和通用定时器都拥有4个输出比较通道(CH1-CH4),并且公用一个CNT计数器
  • 高级定时器前3个通道(CH1-CH3)额外拥有死区生成互补输出的功能,用于驱动三相无刷电机

4.1 PWM波形

  具体PWM介绍见跳转连接
  下面给出高级定时器的前3个输出比较部分
  最左边是CNT和CCR比较的结果,OC1和OC1N就是两个互补端口,分别控制上管和下管的导通和关闭
在这里插入图片描述

  下面给出通用定时器的输出比较部分(高级定时器的第4个输出)
  左边是通过输出模式控制器(TIMx_CCMR1寄存器控制)得到的CNT和CCR的比较结果,输出OC1REF高低电平,随之REF可以映射到主模式的TRGO输出,也可以通过极性选择(TIMx_CCER)到达输出使能电路,最后输出OC1(CH1)引脚

在这里插入图片描述

  下面给出输出模式控制器的执行逻辑—8种
  有效电平高电平无效电平低电平
  PWM模式1和PWM模式2是相反逻辑

在这里插入图片描述

  下面给出PWM的基本执行结构
  左边是定时器配置的部分,只需要比较CNT和CCR的值即可,不需要配置中断。下图中ARR=99,CCR=30

在这里插入图片描述

  PWM的参数计算如下所示

  PWM频率(计数器更新频率) = CK_PSC/(PSC+1)/(ARR+1)
  PWM占空比 = CCR/(ARR+1)
  PWM分辨率 = 1/(ARR+1)

4.2 舵机

4.3 直流电机

  • 电机正接电机正转,电机反接电机反转
  • 直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作(51单片机用达林顿管驱动电机)
  • 电机驱动模块有TB6612、L298N等
  • 下面介绍TB6612,TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并控制其转速方向(不需要反接也可以反转)

  下面就是H桥电路基本结构,由两路推挽电路组成
  左上和右下导通,电流从左到右;右上和左下导通,电流从右到左,可以实现电机正反转

Alt  下面给出TB6612的硬件电路,具体引脚定义见下图

Alt
Alt
  注意电机的驱动电源逻辑电源,驱动电源需要接大电源。STBY接VCC则电机工作,接GND电机不工作。PWMA、AIN2、AIN1、AO1和AO2控制1路电机PWMB、BIN2、BIN1、BO1和BO2控制2路电机

在这里插入图片描述
  如上图所示STBY低电平时,电机不转。IN1和IN2接相同电平,电机不转。O1和O2接相同电平,电机不转
  IN1低电平IN2高电平,PWM给高电平(占空比不为0)电机转。IN1高电平IN2低电平,PWM给高电平(占空比不为0)电机

五、TIM输入捕获IC

  • 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存CCR中(当前CNT的值读出来写入到CCR中),用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
  • 每个高级定时器和通用定时器都拥有4个输入捕获通道
  • 可配置为PWMI模式(PWM输入模式),同时测量频率和占空比
  • 可配合主从模式触发,实现硬件全自动测量

  如下图所示为通道输入部分

  输入引脚部分,有个三输入的异或门,输入接在了通道CH123端口,当三个输入引脚的任何一个有电平翻转时,输出引脚就产生一次电平翻转,输出通过数据选择器,到达输入捕获通道1。如果选择上面一个就是三个输入的异或值。如果选择下面一个,异或门无效,各用各的引脚。

  一旦有边沿(自己配置)出现,输入滤波边沿检测器就会检测到边沿,其中输出有两个通道选择TI1FP1TI2FP2,分别可以输入给两个通道。可以灵活切换后续捕获电路的输入,也可以把一个引脚的输,同时映射到两个捕获单元。

  来到预分频器,对前面的信号进行分预分频,分频之后的触发信号,就可以触发捕获电路进行工作了,每来一个信号,CNT的值就锁存到CCR中,CNT的数值就可以记录两个上升沿的时间间隔

Alt

  下面给出输入捕获/比较通道(细化)
  TI1(CH1)的引脚作为输入,fDTS是滤波器的采样时钟来源,CCMR寄存器里的ICF控制滤波器参数,输出的TI1F就是滤波后的信号,经过边沿检测,CC1P位选择极性,最终得到TI1FP1触发信号,通过数据选择器进入通道1后续的捕获电路,CC1S配置数据选择器,ICPS配置分频器,CC1E位控制使能,最终CNT的值就到了CCR里,并且每捕获一次CNT的值,就需要清零CNT,以便于下一次捕获
在这里插入图片描述

  TIFP1和TI1的边沿信号都可以通向从模式控制器,触发从模式,从模式里面可以自动完成CNT的清零

  下图是主从触发模式的框图
  它可以完成硬件的自动化操作。主模式可以将定时器内部的信号映射到TRGO引脚,用于触发别的外设。从模式就是接收其他外设或者自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制。触发源选择就是选择从模式的触发信号源,选择指定的一个信号,得到TRGI,TRGI去触发从模式,从模式在列表里选择一项操作去自动执行 (例:让TI1FP1信号自动触发CNT清零,触发源选择就可以选中TI1FP1,从模式执行Reset的操作,自动清零CNT,实现硬件全自动测量)

在这里插入图片描述

5.1 频率测量

  首先给出一个频率逐渐降低的方波信号,如下图所示

在这里插入图片描述

  • 测频法:在闸门时间T内,对上升沿进行计次,得到N,则频率为N/T。适合高频信号

  闸门时间通常设置为1s,在1s时间内,对信号的上升沿计次,从0开始,每来一个上升沿(1个周期信号),计次+1。在1s时间内来了多少个周期,频率就是多少Hz。

  • 测周法:在两个上升沿内,以标准频率fc计次,得到N,则频率为fc/N。适合低频信号

  捕获两个上升沿,之间持续的时间就是一个周期。用一个已知的标准频率fc来计次,驱动计数器,从一个上升沿开始计,计数器从0开始,一直计到下一个上升沿,停止。计一个数的时间是1/fc,计N个数,时间就是N/fc,所以频率就是fc/N

  • 中界频率(频率多高算高,多低算低,选用哪种方法):测频法和测周法误差相等的频率点为根号fc/T

  当待测信号频率小于中界频率时,测周法误差小;反之,测频法误差小

5.2 输入捕获/PWMI基本结构

5.2.1 输入捕获基本结构

  下图是输入捕获基本结构,只使用了一个通道,只能测量频率
  时基单元配置好,启动定时器,CNT在预分频之后的时钟驱动下,不断自增,CNT就是测周法用来计时的,标准频率=72MHz/预分频系数。输入捕获通道1的GPIO口,输入方波信号,经过滤波器边缘检测,选择TI1FP1为上升沿触发,当出现上升沿后,CNT的当前计数值进入CCR1里,同时触发源选择,选中TI1FP1为触发信号,从模式选中复位操作,CNT自动清零(CNT的值先进CCR1里,再清零),输入直连,分频器不分频

在这里插入图片描述

5.2.2 PWMI基本结构

  下图是PWMI基本结构,使用两个通道同时捕获一个引脚,可以同时测量频率和占空比
  TI1FP1同上面一样,正常测量频率,TI1FP2设置为下降沿触发,通过交叉通道,触发通道2的捕获单元。CCR2不触发CNT清零,CCR1触发CNT清零,所以CCR2就是高电平计数值,CCR1是整个周期计数值,所以CCR2/CCR1就是占空比

在这里插入图片描述

六、编码器

  • 编码器接口可以接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或者自减,从而指示编码器的位置、旋转方向和旋转速度
  • 每个高级定时器和通用定时器都拥有一个编码器接口
  • 两个输入引脚借用了输入捕获的通道1通道2

6.1 正交编码器方向

  下面给出正交编码器的正反转判断图,编码器接口涉及逻辑就是首先把A相和B相的所有边沿作为计数器的计数时钟,出现边沿信号时,计数自增或自减,是由另一相的状态来决定。就是当出现某一个边沿时,我们判断另一相的高低电平

  • 正转

在这里插入图片描述
在这里插入图片描述

  • 反转

在这里插入图片描述
在这里插入图片描述
  下面给出计数方向和编码器信号的关系(均不反相)
  从表中看出,当TI1计数时,B相(TI2FP2)的边沿不计数,只在A相(TI1FP1)的边沿计数,正转向上计数,反转向下计数

在这里插入图片描述

  下面给出编码器TI1和TI2上计数的操作实例。毛刺部分是当一个信号电平不变的情况下,另一个信号会使得计数器加减加减,保持计数值不变,过滤噪声

在这里插入图片描述

6.2 编码器电路和基本结构

  如下图所示是编码器接口电路图,编码器接口的两个输入端TI1FP1和TI2FP2,分别接到编码器的A相和B相,编码器的输出部分相当于从控制器,去控制CNT的计数时钟计数方向,由上述表控制CNT自增还是自减

在这里插入图片描述

  其中TI1FP1和TI2FP2对应在CH1和CH2通道,输入滤波和边沿检测也需要使用,后面的交叉、预分频、CCR寄存器与编码器无关

在这里插入图片描述

  下面给出编码器的基本结构图
  输入捕获的CH1和CH2通过GPIO接到编码器的AB相,然后通过滤波器极性选择产生TI1FP1和TI2FP2,通向编码器接口,编码器接口通过预分频器控制CNT计数器的时钟,同时还根据编码器的旋转方向,控制CNT的计数方向,编码器正转时,CNT自增,编码器反转时,CNT自减,ARR设置成最大量程

在这里插入图片描述

七、代码编写

7.1 定时器库函数介绍

  介绍一下定时器的部分库函数,打开stm32f10x_tim.h

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//时基单元初始化函数
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//时基单元结构体变量赋一个默认值void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);//使能计数器
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);//使能中断输出信号void TIM_InternalClockConfig(TIM_TypeDef* TIMx);//选择内部时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//选择ITRx其他定时器的时钟
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,uint16_t TIM_ICPolarity, uint16_t ICFilter);//选择TIx捕获通道的时钟
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);//选择ETR通过外部时钟模式1输入的时钟
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);//选择ETR通过外部时钟模式2输入的时钟
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);//单独配置ETR引脚的预分频器、极性、滤波器参数void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);//单独写预分频值
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);//改变计数器的计数模式
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);//自动重装器预装功能配置
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);//给计数器写入一个值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);//给自动重装器写入值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);//获取当前计数器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);//获取当前预分频器值
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);//获取定时器的中断标志位
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);//清除定时器的中断标志位
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);//获取挂起中断标志位
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);//清除挂起中断标志位
//输出比较
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);//初始化输出比较单元
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);//高级定时器输出PWM时,使能主输出
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);//给输出比较结构体默认值
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);//单独设置输出比较的极性
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);//高级定时器互补通道配置
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);//单独修改输出使能
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);//单独更改输出比较模式
//输入捕获
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);//输入捕获初始化,四个通道共用一个函数
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);//输入捕获初始化,快速配置两个通道
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);//输入捕获结构体赋初值
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//选择输入触发源TRGI-从模式触发源
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);//选择输出触发源TRGO-主模式
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);//选择从模式
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);//单独配置通道1的分频器
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);//读CCR的值
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);//单独写CCR寄存器值
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,int16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);//定时器编码器接口配置

7.2 定时器定时中断代码

  使用定时器中断,首先需要初始化定时器
  步骤如下:RCC开启时钟(定时器基准时钟和整个外设时钟都会打开) — 选择时基单元的时钟源(内部时钟和外部时钟) — 配置时基单元(PSC、CNT、ARR) — 配置输出中断控制(使能中断输出,允许更新中断输出到NVIC) — 配置NVIC(打开定时器中断的通道,并分配优先级) — 运行控制(使能计数器) — 定时器中断函数
  下面给出Timer.c,每隔1s进入中断

#include "stm32f10x.h"                  // Device header/*
函数功能:TIM2定时器中断(T = 1s)
*/
void Timer_Init()
{//开启RCC时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM2);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数:对72MHz进行7200分频,得到10k的计数频率,计10000个数字,就是1s -> 1Hz = 1sTIM_TimeBaseInitStructure.TIM_Period = 10000-1;	//ARR自动重装器的值TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;		//PSC预分频器的值 72MHz/(PSC+1)/(ARR+1)TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//使能更新中断TIM_ClearFlag(TIM2,TIM_FLAG_Update);		//避免刚初始化完就进入中断TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);		//使能更新中断函数//NVIC配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);//启动定时器TIM_Cmd(TIM2,ENABLE);		//使能计数器
}/*
函数功能:定时器TIM2中断函数
*/
//void TIM2_IRQHandler()
//{
//	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
//	{
//		
//		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
//	}
//}

7.3 定时器外部时钟申请

  使用定时器中断,首先需要初始化定时器
  步骤如下:RCC开启时钟(定时器基准时钟和整个外设时钟都会打开) — 选择时基单元的时钟源(内部时钟和外部时钟) — 配置时基单元(PSC、CNT、ARR) — 配置输出中断控制(使能中断输出,允许更新中断输出到NVIC) — 配置NVIC(打开定时器中断的通道,并分配优先级) — 运行控制(使能计数器) — 定时器中断函数
  下面给出Timer.c,对射红外传感器挡一次计数器加1,计到第10次,进入中断

#include "stm32f10x.h"                  // Device header/*
函数功能:外部时钟(对射红外传感器)定时器中断
*/
void Timer_Init()
{//开启RCC时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-ETR引脚的外部时钟模式2配置TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0F);		//滤波器0x00-0x0F给最大,比较准确//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数TIM_TimeBaseInitStructure.TIM_Period = 10-1;	//ARR自动重装器的值 计0-9 10个数字TIM_TimeBaseInitStructure.TIM_Prescaler = 1-1;		//外部时钟提供,不是内部的72MHz,如果分频更大,则需要触发多次,计数器才会加1TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//使能更新中断TIM_ClearFlag(TIM2,TIM_FLAG_Update);		//避免刚初始化完就进入中断TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//NVIC配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);//启动定时器TIM_Cmd(TIM2,ENABLE);
}uint16_t Timer_GetCounter()
{return TIM_GetCounter(TIM2);
}//void TIM2_IRQHandler()
//{
//	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
//	{
//		
//		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
//	}
//}

  对于外部时钟源来控制定时器需要了解,本质上是将外部引脚给的方波信号作为时钟来完成功能,以对射红外为例,预分频数值越大,方波信号被分频,导致频率小周期变大,所以挡一次计数器不会加1,需要多挡几次,达到周期时间,计数器才会加1

  外部中断使用外部时钟源的定时器中断也有不同。前者是直接在GPIO口获取到中断电平要求,执行操作;后者是达到定时器重装值,计数器溢出触发中断,执行操作

7.4 定时器输出比较代码

  定时器输出PWM基本步骤如下:RCC开启时钟(TIM和GPIO) — 时钟源选择加配置时基单元配置输出比较单元(CCR的值、输出比较模式、极性选择、输出使能参数 — 配置GPIO(复用推挽输出) — 运行控制(启动计数器)
  同一个定时器可以使用多个通道输出多个PWM,多个通道公用一个计数器,所以它们的频率是相同的,占空比由各自的CCR决定,计数器更新,所有PWM同时跳变,相位同步

7.4.1 PWM驱动LED呼吸灯

  LED正极接在PA0(CH1)口,负极接GND,占空比越大,LED越亮
  选取GPIO模式的时候,需要选取复用推挽输出才可以将引脚的控制权交给片上外设,PWM才可以通过引脚输出
  下面给出PWM.c

#include "stm32f10x.h"                  // Device headervoid PWM_Init()
{//开启RCC时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化(CH1)GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//复用推挽输出模式(在此模式下引脚的控制权才可以交给片上外设,PWM才可以通过引脚输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM2);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数---频率1000HzTIM_TimeBaseInitStructure.TIM_Period = 100-1;		//ARRTIM_TimeBaseInitStructure.TIM_Prescaler = 720-1;		//PSC TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//配置输出比较单元(PA0是OC1输出口,使用OC1初始化函数)TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(&TIM_OCInitStructure);		//给结构体赋初始值TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;		//设置输出比较模式	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//设置输出比较极性TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;		//设置输出使能TIM_OCInitStructure.TIM_Pulse = 0;		//CCR ,占空比为0%TIM_OC1Init(TIM2,&TIM_OCInitStructure);//启动定时器TIM_Cmd(TIM2,ENABLE);		//使能计数器,PWM波形通过PA0输出
}/*
函数功能:单独设置CCR的值,调整占空比
*/
void PWM_SetCompare(uint16_t Compare)
{TIM_SetCompare1(TIM2, Compare);
}

  主函数中调用可调CCR函数,调整占空比

while(1){for(i = 0;i<=100;i++){PWM_SetCompare(i);		//逐渐变亮Delay_ms(10);}for(i = 0;i<=100;i++){PWM_SetCompare(100-i);		//逐渐变暗Delay_ms(10);}}

7.4.2 PWM驱动舵机

  舵机信号输出线接在PA1(CH2)口
  下面给出PWM.h,我们需要设置周期为20ms,高电平时间对应从0.5ms-2.5ms。ARR和PSC决定周期,CCR和ARR决定占空比

#include "stm32f10x.h"                  // Device headervoid PWM_Init()
{//开启RCC时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化(CH1)GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//复用推挽输出模式(在此模式下引脚的控制权才可以交给片上外设,PWM才可以通过引脚输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM2);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数---频率50Hz   72000000/72/20000 = 50HZ  周期为20msTIM_TimeBaseInitStructure.TIM_Period = 20000-1;		//ARRTIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;		//PSC TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//配置输出比较单元(PA0是OC1输出口,使用OC1初始化函数)TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(&TIM_OCInitStructure);		//给结构体赋初始值TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;		//设置输出比较模式	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//设置输出比较极性TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;		//设置输出使能TIM_OCInitStructure.TIM_Pulse = 0;		//CCR ,占空比为0%TIM_OC2Init(TIM2,&TIM_OCInitStructure);//启动定时器TIM_Cmd(TIM2,ENABLE);		//使能计数器,PWM波形通过PA0输出}/*
函数功能:单独设置CCR的值,调整占空比 20000对应20ms,Compare = 500--0.5ms  2500--2.5ms
*/
void PWM_SetCompare(uint16_t Compare)
{TIM_SetCompare2(TIM2, Compare);
}

  将舵机封装成函数,并在主函数调用,实现按键每按一下,舵机转动45°,并在OLED显示屏上显示出度数

#include "stm32f10x.h"                  // Device header
#include "PWM.h"void Servo_Init()
{PWM_Init();
}/*
0°x   -- 500y
180° -- 2500  
y = kx + bb = 500  
180k+500 = 2500
y = x/180*2000 + 500函数功能:输入角度,舵机旋转固定角度
形式参数:转动的角度
*/
void Servo_SetAngle(float Angle)
{PWM_SetCompare(Angle/180*2000 + 500);
}
uint8_t KeyNum;
float Angle;int main(void)
{OLED_Init();Servo_Init();Key_Init();		//这里不初始化,舵机会乱转Servo_SetAngle(0);OLED_ShowString(1, 1, "Angle:");while(1){KeyNum = Key_GetNum();if(KeyNum == 1){Angle += 45;if(Angle >180)Angle = 0;Servo_SetAngle(Angle);}OLED_ShowNum(1, 7, Angle,3);}
}

7.4.3 PWM驱动直流电机

  驱动直流电机需要通过电机驱动电路,这里使用的TB6612模块
  接线如下,注意的是PWM输出口接的是PA2口,对应的是TIM2的通道3,需要更改PWM.h里的输出通道

在这里插入图片描述  下面给出Motor.c

#include "stm32f10x.h"                  // Device header
#include "PWM.h"void Motor_Init()
{PWM_Init();//电机控制方向的GPIO口 PA4 PA5RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_Initstructure;GPIO_Initstructure.GPIO_Mode = GPIO_Mode_Out_PP;		//推挽输出GPIO_Initstructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_Initstructure);
}/*
函数功能:设置电机转动方向和速度
*/
void Motor_SetSpeed(int8_t Speed)
{if(Speed >= 0)		//正转{GPIO_SetBits(GPIOA,GPIO_Pin_4);		//IN1-PA4高GPIO_ResetBits(GPIOA,GPIO_Pin_5);		//IN2-PA5低PWM_SetCompare(Speed);}else		//反转{GPIO_ResetBits(GPIOA,GPIO_Pin_4);		//IN1-PA4低GPIO_SetBits(GPIOA,GPIO_Pin_5);		//IN2-PA5高PWM_SetCompare(-Speed);}
}

  主函数调用,使用按键去控制电机的转速,将速度显示在OLED屏幕上

int main(void)
{OLED_Init();Key_Init();		//必须初始化,不初始化的情况下,会乱转Motor_Init();OLED_ShowString(1, 1, "Speed:");while(1){KeyNum = Key_GetNum();if(KeyNum == 1){Speed += 20;if(Speed >100)Speed = 0;Motor_SetSpeed(Speed);}OLED_ShowNum(1, 7, Speed,3);}
}

7.5 定时器输入捕获代码

  信号输入是由自己生成,所以直接连接单片机的GPIO口,将PA6和PA0接起来,PA0输出方波信号PA6检测输入捕获

7.5.1 输入捕获模式测频率

  首先使用定时器配置好PA0(TIM2_CH1)口输出一个方波信号,这里是产生一个1000Hz,占空比为50%的方波信号
  然后再配置PA6(TIM3_CH1)口,实现输入捕获,具体步骤如下:配置RCC时钟(GPIO和TIM) — GPIO初始化(输入模式) — 配置时基单元配置输入捕获单元(滤波器,极性、直连还是交叉、分频器等) — 从模式触发源,并执行Reset操作 — 开启定时器
  当要读取最新一个周期频率时,读取CCR,按照fc/N得到
  首先给出PWM.c,此函数内主要是通过PA0口输出一个模拟的方波信号,就是利用定时器输出一个波形

#include "stm32f10x.h"                  // Device headervoid PWM_Init()
{//开启RCC时钟PA0口输出方波信号RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化(CH1)GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//复用推挽输出模式(在此模式下引脚的控制权才可以交给片上外设,PWM才可以通过引脚输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM2);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数---调节PSC来调节频率TIM_TimeBaseInitStructure.TIM_Period = 100-1;		//ARRTIM_TimeBaseInitStructure.TIM_Prescaler = 720-1;		//PSC TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//配置输出比较单元(PA0是OC1输出口,使用OC1初始化函数)TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(&TIM_OCInitStructure);		//给结构体赋初始值TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;		//设置输出比较模式	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//设置输出比较极性TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;		//设置输出使能TIM_OCInitStructure.TIM_Pulse = 0;		//CCR,占空比为0%TIM_OC1Init(TIM2,&TIM_OCInitStructure);//启动定时器TIM_Cmd(TIM2,ENABLE);		//使能计数器,PWM波形通过PA0输出}/*
函数功能:单独设置CCR的值,调整占空比
*/
void PWM_SetCompare(uint16_t Compare)
{TIM_SetCompare1(TIM2, Compare);
}
/*
函数功能:单独设置PSC的值,调整频率
*/
void PWM_SetPrescaler(uint16_t Prescaler)
{TIM_PrescalerConfig(TIM2, Prescaler,TIM_PSCReloadMode_Immediate);
}

  然后给出IC.c,此函数内主要是通过PA6(TIM3_CH1)口输入捕获到PA0口的方波信号

#include "stm32f10x.h"                  // Device headervoid IC_Init()
{//开启RCC时钟P6口输入方波,TIM2输出PWM波,TIM3输入捕获RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化TIM3的CH1为PA6GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		//输入模式:上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM3);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数TIM_TimeBaseInitStructure.TIM_Period = 65536-1;		//防止计数溢出,给最大TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;		//标准频率1MHzTIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化输入捕获单元TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;		//TIM3的CH1TIM_ICInitStructure.TIM_ICFilter = 0xF;		//滤波TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//上升沿触发TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;		//不分频TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;		//直连通道TIM_ICInit(TIM3,&TIM_ICInitStructure);//主从触发源选择TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);		//TIM3 ,TI1FP1TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);		//从模式 Reset//启动定时器TIM_Cmd(TIM3,ENABLE);
}
/*
函数功能:读取最新一个周期的频率,f = fc/N = 72M/PSC+1/N(N=CCR)
*/
uint32_t IC_GetFreq()
{return 1000000 / (TIM_GetCapture1(TIM3)+1);		//面向结果编程+1
}

  在主函数中,首先设置好PA0口的输出波形,再捕获PA6的输入频率

//PA0输出频率1000Hz,占空比50%的方波信号PWM_SetPrescaler(720 - 1);		//f = 72M / PSC+1 / ARR+1 = 1000Hz PWM_SetCompare(50);		//D = CCR / ARR+1 = 50%while(1){OLED_ShowNum(1,6,IC_GetFreq(),5);}

7.5.2 PWMI模式测频率和占空比

  同时测频率和占空比时,与上述不同的部分是,要配置好两个通道,之后用CCR2/CCR1得到占空比大小,PWM.c没有变化
  下面给出IC.c文件

#include "stm32f10x.h"                  // Device headervoid IC_Init()
{//开启RCC时钟P6口输入方波,TIM2输出PWM波RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化TIM3的CH1为PA6GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		//输入模式:上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM3);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数TIM_TimeBaseInitStructure.TIM_Period = 65536-1;		//防止计数溢出,给最大TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;		//标准频率1MHzTIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化输入捕获单元TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;		//TIM3的CH1TIM_ICInitStructure.TIM_ICFilter = 0xF;		//滤波TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//上升沿触发TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;		//不分频TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;		//直连通道TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);		//直接配置第二通道(初始化一个通道即可,另一个通道会直接初始化成相反的配置),下降沿触发,交叉//主从触发源选择TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);		//TIM3 ,TI1FP1TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);		//从模式 Reset//启动定时器TIM_Cmd(TIM3,ENABLE);
}
/*
函数功能:读取最新一个周期的频率,f = fc/N = 72M/PSC+1/N(N=CCR1)
*/
uint32_t IC_GetFreq()
{return 1000000 / (TIM_GetCapture1(TIM3)+1);		//面向结果编程+1
}
/*
函数功能:获取PWM,CCR2/CCR1
*/
uint32_t IC_GetDuty()
{return (TIM_GetCapture2(TIM3)+1)*100/(TIM_GetCapture1(TIM3)+1);
}

  同样在主函数中调用,如下所示

//PA0输出频率1000Hz,占空比50%的方波信号PWM_SetPrescaler(720 - 1);		//f = 72M / PSC+1 / ARR+1 = 1000Hz PWM_SetCompare(50);		//D = CCR / ARR+1 = 50%while(1){OLED_ShowNum(1,6,IC_GetFreq(),5);OLED_ShowNum(2,6,IC_GetDuty(),3);}

7.6 编码器接口测速

  本节使用旋转编码器,AB相选择接到PA6(TIM3_CH1)PA7(TIM3_CH2)输入引脚
  代码步骤如下:开启RCC时钟(GPIO和TIM时钟)配置GPIO(PA6和PA7输入模式)配置时基单元配置输入捕获单元(滤波器和极性)配置编码器接口模式启动定时器
  代码主要功能是测旋转编码器旋转的速度,利用TIM2去每隔1s读取速度,显示在OLED上

#include "stm32f10x.h"                  // Device headervoid Encoder_Init()
{//开启RCC时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化TIM3的CH1\CH2为PA6\PA7GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		//输入模式:上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//配置时基单元--编码器时钟驱动计数器TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数TIM_TimeBaseInitStructure.TIM_Period = 65536-1;		//最大量程TIM_TimeBaseInitStructure.TIM_Prescaler = 1-1;		//不分频TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化输入捕获单元TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICStructInit(&TIM_ICInitStructure);		//结构体配置不完整,赋一个初始值TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;		//TIM3的CH1-PA6TIM_ICInitStructure.TIM_ICFilter = 0xF;		//滤波//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//高低电平极性不反转(不反相)TIM_ICInit(TIM3,&TIM_ICInitStructure);TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;		//TIM3的CH2-PA7TIM_ICInitStructure.TIM_ICFilter = 0xF;		//滤波//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//高低电平极性不反转(不反相)TIM_ICInit(TIM3,&TIM_ICInitStructure);//配置编码器接口TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);		//任意一个极性反转,正转就是增//打开定时器TIM_Cmd(TIM3,ENABLE);
}int16_t Encoder_Get()		//有符号
{int16_t Temp;Temp = TIM_GetCounter(TIM3);TIM_SetCounter(TIM3,0);		//CNT清零return Temp;
}

  在主函数利用中断,1s检测一次

int16_t Speed;int main(void)
{OLED_Init();Timer_Init();Encoder_Init();OLED_ShowString(1,1,"Speed:");while(1){OLED_ShowSignedNum(1,7,Speed,5);}
}/*
函数功能:定时器TIM2中断函数 1s
*/
void TIM2_IRQHandler()
{if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){Speed = Encoder_Get();TIM_ClearITPendingBit(TIM2,TIM_IT_Update);}
}

相关文章:

初识STM32单片机-TIM定时器

初识STM32单片机-TIM定时器 一、定时器概述二、定时器类型2.1 基本定时器(TIM6和TIM7)2.2 通用定时器(TIM2、TIM3、TIM4和TIM5)2.3 高级定时器(TIM1和TIM8) 三、定时中断基本结构和时基单元工作时序3.1 定时器基本结构3.2 预分频器时序3.3 计数器时序3.3.1 计数器有无预装时序(…...

NSSCTF-Web题目3

目录 [BJDCTF 2020]easy_md5 1、知识点 2、题目 3、思路 [ZJCTF 2019]NiZhuanSiWei 1、知识点 2、题目 3、思路 第一层 第二层 第三层 [BJDCTF 2020]easy_md5 1、知识点 弱比较&#xff0c;强比较、数组绕过、MD5加密 2、题目 3、思路 1、首先我们跟着题目输入&a…...

基于Java实现震中附近风景区预警可视化分析实践

目录 前言 一、空间数据说明 1、表结构信息展示 2、空间范围查询 二、Java后台开发实现 1、模型层设计与实现 2、控制层设计与实现 三、Leaflet地图开发 1、地震震中位置展示 2、百公里风景区列表展示 3、风景区列表展示 4、附近风景区展示 四、总结 前言 地震这类…...

【CTF Web】CTFShow web7 Writeup(SQL注入+PHP+进制转换)

web7 1 阿呆得到最高指示&#xff0c;如果还出问题&#xff0c;就卷铺盖滚蛋&#xff0c;阿呆心在流血。 解法 注意到&#xff1a; <!-- flag in id 1000 -->拦截很多种字符&#xff0c;连 select 也不给用了。 if(preg_match("/\|\"|or|\||\-|\\\|\/|\\*|\…...

ABAP WRITE换行输出

不换行&#xff1a; write hello world!. write 春天马上就到了!. 换行&#xff1a; write hello world!. write /春天马上就到了!. 换行的第二种&#xff1a; write: hello world! ,/,春天马上就到了!....

VUE3学习第一篇:启动ruoyi

1、找到ruoyi的vue3版本 然后下载代码到本地&#xff0c; 我刚开始用的nodejs14报错&#xff0c; 后面换成nodejs16&#xff0c;启动前端成功了。 页面如下图所示...

python-数据可视化(总)

python-数据可视化 ** 数据可视化 指的是通过可视化表示来探索数据&#xff0c;它与数据挖掘**紧密相关&#xff0c;而数据挖掘指的是使用代码来探索数据集的规律和关联。数据集可以是用一行代码就能表示的小型数字列表&#xff0c;也可以是数以吉字节的数据 最流行的工具之一…...

使用git生成SSH公钥,并设置SSH公钥

1、在git命令行里输入以下命令 ssh-keygen -t rsa 2、按回车&#xff0c;然后会看到以下字眼 Generating public/private rsa key pair. Enter file in which to save the key (/c/Users/xxx/.ssh/id_rsa) 例&#xff1a; 3、继续回车&#xff0c;然后会看到以下字眼 Enter…...

iPhone win10更改备份路径

1 删掉或改名 旧的Backup, 否则不能连接 ren "C:\Users\Administrator\Apple\MobileSync\Backup" Backup_old 2 在目标盘中 创建新的文件夹 手动创建 MobileSync\Backup 3 链接两个文件夹 mklink /J "C:\Users\Administrator\Apple\MobileSync\Backup" &qu…...

Jmeter元件及基本作用域

&#x1f680;从今天开始学习性能测试工具——Jmeter&#xff0c;小梦也是先学习了下Jmeter的元件概念以及其基本的作用域&#xff0c;整理了下笔记&#xff0c;希望不管是从事开发领域还是测试领域的朋友们&#xff0c;我们一起学习下Jmeter工具&#xff0c;提升工作中的技能&…...

GB-T 43696-2024 网络安全技术 零信任参考体系架构

GB-T 43696-2024 网络安全技术 零信任参考体系架构 编写背景 随着网络环境的日益复杂&#xff0c;传统的网络安全策略已经难以满足现代企业的需求。为了应对不断变化的安全威胁&#xff0c;零信任安全模型应运而生。GB-T 43696-2024《网络安全技术 零信任参考体系架构》是中国…...

Java使用apache.poi生成excel插入word中

加油&#xff0c;新时代打工人&#xff01; 工作需求&#xff0c;上个文章我们生成好的word&#xff0c;这次将生成好的excel表格数据&#xff0c;插入word中。需要准备好excle数据&#xff0c;然后插入到word中。 最后个需要&#xff0c;就是把这些生成好的word文档转成pdf进行…...

斯坦福报告解读3:图解有趣的评估基准(上)

《人工智能指数报告》由斯坦福大学、AI指数指导委员会及业内众多大佬Raymond Perrault、Erik Brynjolfsson 、James Manyika等人员和组织合著&#xff0c;旨在追踪、整理、提炼并可视化与人工智能&#xff08;AI&#xff09;相关各类数据&#xff0c;该报告已被大多数媒体及机构…...

C语言---扫雷游戏的实现

1.扫雷游戏的分析和设计 需要创建3个文件夹 test.c----扫雷游戏的测试 game.c----扫雷游戏的实现 game.h----扫雷游戏的实现 雷的信息使用二维数组存放 • 使⽤控制台实现经典的扫雷游戏 • 游戏可以通过菜单实现继续玩或者退出游戏 • 扫雷的棋盘是9*9的格⼦ • 默认…...

《征服数据结构》块状链表

摘要&#xff1a; 1&#xff0c;块状链表的介绍 2&#xff0c;块状链表的代码实现&#xff08;Java和C&#xff09; 1&#xff0c;块状链表的介绍 前面我们讲过数组和链表&#xff0c;数组具有 O(1)的查询时间&#xff0c;O(N)的删除&#xff0c;O(N)的插入&#xff0c;而链表具…...

leetCode.86. 分隔链表

leetCode.86. 分隔链表 题目思路&#xff1a; 代码 class Solution { public:ListNode* partition(ListNode* head, int x) {auto lh new ListNode(-1), rh new ListNode(-1);auto lt lh, rt rh;for(auto p head; p; p p->next ) {if(p->val < x) {lt lt->…...

Java进阶学习笔记5——Static应用知识:单例设计模式

设计模式&#xff1a; 架构师会使用到设计模式&#xff0c;开发框架&#xff0c;就需要掌握很多设计模式。 在Java基础阶段学习设计模式&#xff0c;将来面试笔试的时候&#xff0c;笔试题目会经常靠到设计模式。 将来会用到设计模式。框架代码中会用到设计模式。 什么是设计…...

Vue 前端加框 给div加红色框框 js实现

实现方式&#xff1a;用getElementsByClassName、createElement、appendChild实现在原有div上添加一个新的div&#xff0c;从而达到框选效果 <template><div><el-button click"addIten">添加</el-button><el-button click"deleteIt…...

Percona Toolkit 神器全攻略(实用类)

Percona Toolkit 神器全攻略&#xff08;实用类&#xff09; Percona Toolkit 神器全攻略系列共八篇&#xff0c;前文回顾&#xff1a; 前文回顾Percona Toolkit 神器全攻略 全文约定&#xff1a;$为命令提示符、greatsql>为GreatSQL数据库提示符。在后续阅读中&#xff0c;…...

ARM GIC 和NVIC的区别

ARM GIC&#xff08;Generic Interrupt Controller&#xff09;和NVIC&#xff08;Nested Vectored Interrupt Controller&#xff09;是两种不同的中断控制器&#xff0c;它们在ARM架构中扮演着重要的角色&#xff0c;但各自有不同的设计和应用场景。 ARM GIC&#xff1a; G…...

CSS文本粒子动画特效之爱心粒子文字特效-Canvas

1. 效果图 2.完整代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><style>body,html {margin: 0;paddin…...

小熊家务帮day5 客户管理模块1 (小程序认证,手机验证码认证等)

客户管理模块 1.认证模块1.1 认证方式介绍1.1.1 小程序认证1.1.2 手机验证码登录1.1.3 账号密码认证 1.2 小程序认证1.2.1 小程序申请1.2.2 创建客户后端工程jzo2o-customer1.2.3 开发部署前端1.2.4 小程序认证流程1.2.4.1 customer小程序认证接口设计Controller层Service层调用…...

Blender 学习笔记(一)快捷键记录

Blender 的快捷键映射非常强大&#xff0c;如果学会将会快速提高工作效率&#xff0c;本文抄自 Blender 4.1 Manual&#xff0c;基于 Blender 4.1&#xff0c;因为自己使用 Windows&#xff0c;所以只记录 Windows 相关快捷键。 全局快捷键 键位作用ctrl0打开文件ctrls保存文…...

ubuntu linux (20.04) 源码编译cryptopp库 - apt版本过旧

下载最新版 https://www.cryptopp.com/#download 编译安装&#xff1a; ​#下载Cryptopp源码 #git clone https://gitee.com/PaddleGitee/cryptopp.git#进入文件夹 cd cryptopp #编译&#xff0c;多cpu处理 make -j8 #安装&#xff0c;默认路径&#xff1a;/usr/local sudo m…...

机器学习-3-特征工程的重要性及常用特征选择方法

参考特征重要性:理解机器学习模型预测中的关键因素 参考[数据分析]特征选择的方法 1 特征重要性 特征重要性帮助我们理解哪些特征或变量对模型预测的影响最大。 特征重要性是数据科学中一个至关重要的概念,尤其是在建立预测性任务的模型时。想象你正在尝试预测明天是否会下…...

QGis3.34.5工具软件保存样式,软件无反应问题

在使用QGis软件保存SLD样式的时候&#xff0c;每次保存样式&#xff0c;软件都进入无反应状态&#xff0c;导致无法生成样式文件 百度中多次查询问题点&#xff0c;终未能在在3.34.5这个版本上解决问题。 考虑到可能是软件本身问题&#xff0c;于是删除了3.34.5这个版本&#x…...

JavaScript(ES6)入门

ES6 1、介绍 ECMAScript 6&#xff08;简称ES6&#xff09;是于2015年6月正式发布的JavaScript 语言的标准&#xff0c;正式名为ECMAScript 2015&#xff08;ES2015&#xff09;。它的目标是使得JavaScript语言可以用来编写复杂的大型应用程序&#xff0c;成为企业级开发语言。…...

深入分析 Android Activity (十)

文章目录 深入分析 Android Activity (十)1. Activity 的资源管理1.1 使用资源 ID 访问资源1.2 Drawable 资源1.3 使用 TypedArray 管理资源1.4 使用资源配置 2. Activity 的数据存储2.1 SharedPreferences2.2 文件存储2.3 SQLite 数据库2.4 ContentProvider 3. Activity 的性能…...

考试“挂了“用日语怎么说,柯桥商务日语培训

1、もえる 热衷于……&#xff0c;燃烧 除了“燃烧”&#xff0c;还有“热衷于……”的意思&#xff0c;如“家が燃える&#xff08;房子着火了&#xff09;”&#xff0c;“勉強に燃える&#xff08;热衷于学习&#xff09;”。 &#xff21;&#xff1a;今&#xff08;いま&…...

【机器学习300问】103、简单的经典卷积神经网络结构设计成什么样?以LeNet-5为例说明。

一个简单的经典CNN网络结构由&#xff1a;输入层、卷积层、池化层、全连接层和输出层&#xff0c;这五种神经网络层结构组成。它最最经典的实例是LeNet-5&#xff0c;它最早被设计用于手写数字识别任务&#xff0c;包含两个卷积层、两个池化层、几个全连接层&#xff0c;以及最…...