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

微信支付网站开发h5制作小程序有哪些

微信支付网站开发,h5制作小程序有哪些,wordpress 根据分类id获取分类名称,手机网站设计需求分析目录 6、TIM#xff08;Timer#xff09;定时器基本定时器通用定时器高级定时器示例程序#xff08;定时器定时中断定时器外部时钟#xff09;TIM输出比较示例程序#xff08;PWM驱动LED呼吸灯PWM驱动舵机PWM驱动直流电机#xff09;TIM输入捕获示例程序Timer定时器基本定时器通用定时器高级定时器示例程序定时器定时中断定时器外部时钟TIM输出比较示例程序PWM驱动LED呼吸灯PWM驱动舵机PWM驱动直流电机TIM输入捕获示例程序输入捕获模式测频率PWMI模式测频率和占空比TIM编码器接口示例代码编码器接口 6、TIMTimer定时器 STM32中功能最强大、结构最复杂的外设——定时器。 定时器共四个部分分为八个小节笔记。本小节为第一部分第一节。 在第一部分是定时器的基本定时的功能定时中断功能、内外时钟源选择 在第二部分是定时器的输出比较功能最常见的用途是产生PWM波形用于驱动电机等设备 在第三部分是定时器的输入捕获功能和主从触发模式来实现测量方波频率 在第四部分是定时器的编码器接口能够更加方便读取正交编码器的输出波形编码电机测速 实验一定时中断的功能定时器使用内部时钟定了一个一秒的时间每隔一秒申请一下中断然后在中断函数里执行number最后在OLED上显示number。 实验二定时器外部时钟这个程序使用了外部时钟来驱动定时器我们可以在定时器指定的外部引脚上输入一个方波信号来提供定时器计数的时钟。现在这里我暂时用这个对射式红外传感器来手动模拟一个外部时钟。我们用挡光片依次遮挡、移开、遮挡、移开、提供一个方波可以看到OLED上下面这个CNT就是定时器中计数器的值每遮挡、移开一次计数器加一然后计数器寄到九后自动清零同时申请中断执行number。 使用定时器的外部时钟可以提供一个更加精确的时钟来计时或者也可以把外部时钟当做一个计数器 用来统计引脚上电平翻转的次数毕竟定时器本质上就是一个计数器。 TIM简介 TIMTimer定时器可定时触发中断 定时器本质上就是一个计数器当这个计数器的输入是一个准确可靠的基准时钟的时候那他在对这个基准时钟进行计数的过程实际上就是计时的过程。 定时器可以对输入的时钟进行计数在stm32中定时器的基准时钟一般是主频72MHz如果对72MHz记72个数那就是1MHz也就是1us的时间72MHz就是1秒记72M个数可以理解为对72个数计数1M次记72个数的频率就是1MHz用时1us如果记72000个数那就是1KHz也就是1ms的时间并在计数值达到设定值时触发中断。 stm32的定时器拥有16位2的16次方是65536的计数器计数器就是用来执行计数定时的寄存器每来一个时钟计数器加1、16位预分频器可以对计数器的时钟进行分频让计数更加灵活、16位自动重装寄存器是计数的目标值计多少个时钟申请中断这几个寄存器构成了定时器最核心的部分我们把这一块电路称为时机单元在72MHz计数时钟下可以实现最大59.65s的定时(也就是预分频器设置最大自动重装也设置最大)。 为什么在72MHz计数时钟下可以实现最大59.65s的定时? 72M/65536/65536得到的是中断频率然后取倒数就是59.65秒多大家可以自己算一下。 详细解释在定时器中,预分频器和计数器都是16位的,所以它们的最大值是65535,而不是65536。预分频器的最大值决定了计数时钟的频率,而计数器的最大值决定了定时器的最大计数周期。因此,如果预分频器和计数器的最大值都设置为65535,那么定时器的最大时间就是72MHz/65536/65536得到的是中断频率倒数就是中断时间。【最大值是65536但计数是从0~65535】 这就是最大的定时时间应该说还是挺长的了。如果你嫌这个还不够长STM32的定时器还支持级联的模式也就是一个定时器的输出当做另一个定时器的输入这样加一起最大的定时时间就是59.65秒 再乘两次6536这个时间大概是8000多年。如果还嫌短那就再级联一个定时器定时时间还会再延长6536x6536倍这个可见指数爆炸的威力小小的STM32利用3个定时器几年就能实现丈量宇宙年龄的能力。 定时器不仅具备基本的定时中断功能而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。 定时器根据复杂度和应用场景分为了高级定时器、通用定时器(最常用)、基本定时器三种类型。 定时器类型 类型编号总线功能高级定时器TIM1、TIM8APB2拥有通用定时器全部功能并额外具有重复计数器、死区生成、互补输出、刹车输入等功能通用定时器TIM2、TIM3、TIM4、TIM5APB1拥有基本定时器全部功能并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能基本定时器TIM6、TIM7APB1拥有定时中断、主模式触发DAC的功能 除了TIM1-8在库函数中还出现了TIM9、10、11等这些一般都用不到知道就好 三种定时器所连的总线是不一样的其中高级定时器连接的是性能更高的APB2总线通用定时器和 基本定时器连接的是APB1中线这个在RCC开启时钟的时候要注意一下。 最后看一下功能 先看基本定时器它的功能最少只有基本的定时中断功能和一个主模式触发DAC的功能所以基本定时器还可以和DAC联合使用这就是基本定时器还是比较简单的。 然后是通用定时器这就复杂起来了它拥有基本定时器全部功能同时它还额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能这些功能就是我们本课程重点讲的。 最后就是高级定时器了这就更复杂了它拥有通用定时器全部功能同时它还具有重复计数器、死区生成、互补输出、刹车输入等功能这些功能主要是为了三相无刷电机的驱动设计的我们本课程暂时不会涉及到。 STM32F103C8T6定时器资源TIM1、TIM2、TIM3、TIM4也就是一个高级定时器和三个通用定时器没有基本定时器不同的型号定时器的数量是不同的。你在操作这个外设之前一定要查一下它是不是有这个外设别操作到了不存在的外设那样是不会起作用的。 接下来我们就依次来看一下高级定时器、通用定时器和基本定时器的结构图看一下这三种定时器是怎么样来工作的设计这些结构都能完成哪些任务。 基本定时器 理解时基单元的工作流程定时器产生中断的全部流程、主模式触发DAC的功能如下内容 下面这三个构成了最基本的计数计时电路所以这一块电路就叫做时基单元 时基单元预分配器PSC、自动重装载寄存器ARR、计数器CNT 时基单元之前连接的就是基准计数时钟的输入最终来到了这个位置。由于基本定时器只能选择内部时钟所以你可以直接认为这根线直接连到了输入端的这里也就是内部时钟CK_INT内部时钟的来源是RCC_TIMxCLK这里的频率值一般都是系统的主频72MHz所以通向时基单元的计数基准频率就是72MHz。 进入时基单元首先是预分频器PSC它可以对72MHz的计数时钟进行预分频比如预分频器写0就是不分频输出72MHz这时候输出频率等于输入频率等于72MHz写1是进行二分频输出36MHz写2是三分频输出24MHz …所以预分频的值和实际的分频系数相差1即实际分频系数预分频器的值1预分频器是16位的最大值可以写65535也就是最大65536分频。 然后是计数器对预分频后的计数时钟进行计数计数时钟每来一个上升沿计数器的值加1这个计数器的值也是16位的值可以从0一直加到65535如果再加的话计数器就会回到0重新开始。所以计数器的值在计时过程中会不断地自增运行当自增运行到目标值时产生中断那就完成了定时的任务所以还需一个存储目标值的寄存器那就是自动重装载寄存器了。 自动重装寄存器也是16位的它存的是我们写入的计数目标在运行的过程中计数值不断自增自动重装载是固定的目标当计数值等于自动重装值时也就是计时时间到了那它就会产生一个中断信号并且清零计数器计数器自动开始下一次的计数计时。在这里图上画的一个向上的折线箭头就代表这里会产生中断信号像这种计数值等于自动重装值产生的中断叫做“更新中断”这个更新中断之后就会通向NVIC我们再配置好NVIC的定时器通道那定时器的更新中断就能够得到CPU的响应。这里向下的箭头代表的是会产生一个事件这里对应的事件就叫做“更新事件”更新事件不会触发中断但可以触发内部其他电路的工作。 总结定时器产生中断的全部流程从基准时钟到预分频器再到计数器计数器计数自增同时不断地与自动重装寄存器进行比较值相等时即计时时间到这时就会产生一个更新中断和更新事件CPU响应更新中断就完成了我们定时中断的任务了。 下图红圈是一个向上的折线箭头就代表这里会产生中断信号像这种计数值等于自动重装值产生的中断叫做“更新中断”。 下图红圈是一个向下的折线箭头代表的是产生一个事件这里对应的事件就叫做“更新事件”更新事件不会触发中断但可以触发内部其它电路的工作。 主模式触发DAC的功能 下面简单介绍一下后续讲主模式触发DAC的功能STM32定时器的一大特色就是主从触发模式主从触发模式能让内部的硬件在不受程序的控制下实现自动运行如果能把主从触发模式掌握好那在某些情景下将会极大地减轻CPU的负担。 主模式触发DAC的作用就是在我们使用DAC的时候可能会用DAC输出一段波形那就需要每隔一段时间来触发一次DAC让它输出下一个电压点。如果用正常的思路来实现的话就是先设置一个定时器产生中断每隔一段时间在中断程序中调用代码手动触发一次DAC转换然后DAC输出这样会使主程序处于频繁被中断的状态这会影响主程序的运行和其他中断的响应所以定时器就设计了一个主模式使用这个主模式可以把定时器的更新事件映射到触发输出TRGOTrigger Out的位置然后TRGO直接接到DAC的触发转换引脚上这样定时器的更新就不需要再通过中断来触发DAC转换了仅需要把更新事件通过主模式映射到TRGO然后TRGO就会直接区触发DAC整个过程不需要软件的参与实现了硬件自动化这就是主模式的作用当然除了主模式外还有更多硬件自动化的设计后续讲。 这个可编程定时器的主要部分是一个带有自动重装载的16位累加计数器,计数器的时钟通过一个预分频器得到。 软件可以读写计数器、自动重装载寄存器和预分频寄存器,即使计数器运行时也可以操作。时基单元包含 预分频寄存器(TIMx_PSC) 预分频器 预分频可以以系数介于1至65536之间的任意数值对计数器时钟分频,就是对输入的基淮频率提前进行一个分频的操作。它是通过一个16位寄存器(TIMx-PSC)的计数实现分频。因为TIMx-PSC控制寄存器具有缓冲,可以在运行过程中改变它的数值,新的预分频数值将在下一个更新事件时起作用。 假设这个寄存器写0就是不分频或者说是1分频这时候输出频率输入频率72MHz如果预分频器写1那就是2分频输出频率输入频率/236MHz,所以预分频器的值和实际的分频系数相差了1即实际分频系数预分频器的值1。 时序图讲解32:34 注意实际的设置计数器使能信号CNT_EN相对于CEN滞后一个时钟周期。 计数器寄存器(TIMx_CNT) 计数器由预分频输出CK_CNT驱动设置TIMx_CR1寄存器中的计数器使能位(CEN)使能计数器计数。这个计数器可以对预分频后的计数时钟进行计数,计数时钟每来一个上升滑计数器的值就加1,由于这个计数器也是16位的所以里面的值可以从0一直加到65535如果再加的话计数器就会回到0重新开始。所以计数器的值在计时过程中会不断地自增运行当自增运行到目标值时产生中断那就完成了定时的任务所以现在还需要一个存储目标值的寄存器那就是自动重装寄存器了。 通用定时器 通用定时器结构就瞬间复杂了很多。 1.通用定时器与基本定时器异同 首先中间最核心的部分还是时基单元这部分结构和工作流程和基本定时器是一样的不过对于通用定时器而言计数器的计数模式就不止向上计数一种了向上自增通用定时器和高级定时器支持向上计数模式、向下计数模式和中央对齐模式。基本定时器仅支持向上计数模式。最常用的还是向上计数模式。 向下计数模式就是从重装值开始向下自减减到0之后回到重装值同时申请中断然后继续下一轮依次循环 中央对齐模式就是从0开始先向上自增计到重装值申请中断然后再向下自减减到0再申请中断然后继续下一轮依次循环 2.内外时钟源选择和主从触发模式 如下是内外时钟源选择和主从触发模式的结构。 内外时钟源选择对于基本定时器定时只能选择内部时钟也就是系统频率72MHz对于通用定时器时钟源可以选择内部时钟或者外部时钟。 外部时钟的选择有如下四种 第一个外部时钟就是来自TIMx_ETR引脚上的外部时钟TIMx_ETR(External)引脚的位置可以参考引脚定义表中关于默认复用功能和重定义功能的定义如下图所示。可以看到这里有TIM2_CH1_ETR意思就是这个TIM2的CH1和ETR都复用在了引脚PA0上。下面还有其他定时器的引脚CH2、CH3、CH4和其他定时器的一些引脚也可以在表中找到。 这里可以在TIM2的ETR引脚也就是PA0上接一个外部方波时钟然后配置一下内部的极性选择、边沿检测和预分频器电路再配置一下输入滤波电路这些电路可以对外部时钟进行一定的整形因为是外部时钟所以难免会有毛刺这些电路就可以对输入的波形进行滤波同时也可以选择一下极性和预分频器最后滤波后的信号兵分两路上面一路ETRF进入触发控制器紧跟着就可以选择作为时基单元的时钟了在stm32中这一路也叫做‘外部时钟模式2’如图中红线另一路与其他信号通过数据选择器输出TRGITrigger In触发输入从名字上来看它主要是作为触发输入来使用的这个触发输入可以触发定时器的从模式。关于触发输入和从模式的内容之后再涉及本节主要把这个触发输入当作外部时钟来使用的情况你暂且可以把这个TRGI当做外部时钟的输入来看当这个TRGI当作外部时钟来使用时这一路就称为外部时钟模式1如图中黄线所示。 那通过这一路的外部时钟都有哪些呢往左看第一个就是ETR引脚的信号这里ETR引脚既可以通过上面这一路来当做时钟又可以通过下面这一路来当做时钟两种情况对于时钟输入而言是等价的只不过是下面这一路输入会占用触发输入的通道而已。 第二个外部时钟可以是来自其他定时器的信号ITR 主模式的输出TRGO可以通向其他定时器实际上通向的就是ITR引脚通过这一路就可以实现定时器级联的功能。如上黄线所示ITR0到ITR3分别来自其他4个定时器的TRGO输出具体的连接方式如下表所示这就是ITR和定时器的连接关系实现定时器级联功能。例如可以先初始化TIM3然后使用主模式把它的更新事件映射到TRGO上接着再初始化TIM2选择ITR2对应的就是TIM3的TRGO然后后面再选择时钟为外部时钟模式1这样TIM3的更新事件就可以驱动TIM2的时基单元也就是实现了定时器的级联。 第三个外部时钟可来自TIMx_CH1的TI1_EDCH1引脚的边沿即从CH1引脚连接的输入捕获模块获得时钟ED意为Edge边沿意为通过这一路的时钟上升沿和下降沿均有效。 第四个外部时钟可来自TIMx_CH1的TI1FP1和来自TIMx_CH2的TI2FP2 总结一下外部时钟模式1的输入可以是ETR引脚、其他定时器、CH1引脚的边沿、CH1引脚和CH2引脚还是比较复杂的一般情况下外部时钟通过ETR引脚就可以了 下面设置这么复杂的输入不仅仅是为了扩大时钟树的范围更多的还是为了某些特殊应用场景而设计的比如未来定时器的级联而设计的这一部分下面这一部分我们之后讲输入捕获和测频率时还会继续讲的。 对于时钟输入而言最常用的还是内部的72兆赫兹的时钟如果要使用外部时钟首选ETR引脚外部时钟模式2的输入这一路最简单最直接。 编码器模式 最后这里还有一块没有讲到这个是定时器的一个编码器接口(红框下方)可以读取正交编码器的输出波形这个我们后续课程也会再讲。 主模式输出 这部分电路可以把内部的一些事件映射到这个TRGO引脚上比如我们刚才讲基本定时器分析的将更新事件映射到TRGO用于触发DAC。这里也是一样它可以把定时器内部的一些事件映射到这里来用于触发其它定时器、DAC或者ADC可见这个触发输出的范围是比基本定时器更广一些的。 输出比较电路 通用定时器结构图的右下角即为定时器的输出比较功能的结构如下图所示。有四个输出通道分别对应CH1到CH4的引脚可以用来输出PWM波形驱动电机。 输入捕获电路 通用定时器的左下角即为输入捕获电路的结构图它同输出比较功能一样有四个通道对应CH1到CH4引脚。可以用于测量输入方波的频率。因为输入捕获和输出比较不能同时使用故中间的捕获/比较寄存器是输入捕获和输出比较电路共用的CH1到CH4的引脚也是共用的。 那有关输入捕获和输出比较这部分电路在之后具体分析。 高级定时器 高级定时器的大部分结构和通用定时器相同只在部分作了功能拓展。相比于通用定时器拓展了框图右边红圈的内容。 1.重复次数计数器 在申请中断的的地方增加了一个重复次数计数器它的作用是可以实现每隔几个计数周期才发生一次更新事件和中断。原来的结构是每个计数周期完成后就都会发生更新现在这个计数器实现每隔几个周期再更新一次相当于对输出的更新信号又作了一次分频。对于高级定时器我们之前计算的最大定时时间59秒多在这里就还需要再乘一个65536也就是提升了很多的定时时间. 下面部分是高级定时器对输出比较模块的升级了暂时了解即可 2.死区生成电路与三相无刷电机 图中的DTG和DTG寄存器组成死区生成电路右侧的引脚TIMx_CH1/CH2/CH3由原来的每路一个变成了两个互补的输出引脚TIMx_CH1/CH2/CH3和TIMx_CH1N/CH2N/CH3N可以输出一对互补的PWM波。这些电路是为了驱动三相无刷电机设计的。在四轴飞行器、电动车后轮、电钻中都可以发现三相无刷电机。三相无刷电机的驱动电路需要三个桥臂每个桥臂需要2个大功率开关管来控制总共需要6个大功率开关管控制。所以输出的PWM引脚的前三路就变为了互补的输出引脚而第四路TIMx_CH4没有变化。三相电机只需要三路。    为了防止互补输出的PWM驱动桥臂时在开关切换的瞬间由于器件的不理想造成短暂的直通现象故前面添加了死区生成电路。在开关切换的瞬间产生一定时长的死区让桥臂的上下管全部关断防止出现直通现象。 3.刹车输入 刹车输入的主要作用是给电机驱动提供安全保障。如果外部引脚BKINBreak In产生了刹车信号或者内部时钟失效产生了故障控制电路就会自动切断电机的输出防止意外的发生。 定时中断基本结构 定时中断的基本结构如下图所示 首先中间最重要的还是PSC(Prescaler)预分频器、CNT (Counter)计数器、ARR (AutoReloadRegister自动重装器这三个寄存器构成的时基单元。下面是运行控制就是控制寄存器的一些位,比如启动停止、向上或向下计数等等我们操作这些寄存器就能控制时基单元的运行了。 左边是为时基单元提供时钟的部分这里可以选择RCC提供的内部时钟也可以选择ETR引脚提供的外部时钟模式2。在本小节示例程序里第一个定时器定时中断就是用的内部时钟这一路第二个定时器外部时钟就是用的外部时钟模式2这一路。当然还可以选择这里的触发输入当做外部时钟即外部时钟模式1对应的有ETR外部时钟、ITRx其他定时器、Tlx输入捕获通道这些就是定时器的所有可选的时钟源了。最后这里还有个编码器模式这一般是编码器独用的模式普通的时钟用不到这个。 接下来右边这里就是计时时间到产生更新中断后的信号去向。如果是高级定时器的话还会多一个重复计数器。那这里中断信号会先在状态寄存器里置一个中断标志位这个标志位会通过中断输出控制到NVIC申请中断。 为什么会有一个中断输出控制呢 因为这个定时器模块有很多地方都要申请中断。比如上面这个图不仅更新要申请中断这里触发信号也会申请中断还有下面的输入捕获和输出比较匹配时也会申请。所以这些中断都要经过中断输出控制如果需要这个中断那就允许如果不需要那就禁止。简单来说这个中断输出控制就是一个中断输出的允许位如果需要某个中断就记得允许一下。 时基单元运行时序举例 STM32中关于时序运行的内容很多具体请见手册的详细讨论这里仅举一些时基单元的例子作简要分析。 1.预分频器时序分析 图中描述了当预分频器的分频系数从1变为2时计数器的时序图。第一行是CK_PSC是预分频器的输入时钟选内部时钟的话一般是72MHz这个时钟在不断运行下面的CNT_EN是计数器使能高电平计数器正常运行低电平计数器停止再下面是CK_CNT是计数器时钟既是预分频器的时钟输出也是计数器的时钟输入(前面时基框图里有)。开始时计数器未使能计数器时钟不运行然后使能后前半段当计数器使能信号CNT_EN变为高电平后的下一个CK_PSC的高电平定时器时钟CK_CNT接收CK_PSC。且此时预分频器的分频系数为1PSC 0预分频器完成一分频计数器时钟等于预分频前的时钟即CK_PSC CK_CNT后半段预分频系数变为2计数器时钟变为预分频前时钟的一半。 在计数器时钟的驱动下下面的计数器寄存器也跟随时钟的上升沿不断自增当计数器寄存器的值依次递增达到0xFC后立即跳变为0x00说明重装载寄存器ARR设计的目标计数值就是0xFC当计数值记到和重装值相等并且下一个时钟来临时计数值才清零此时电路产生一个更新事件脉冲信号UEV并产生中断信号计数值清0。这就是一个计数周期的工作流程。 2.缓冲影子寄存器 然后是最下面的三行时序描述的是预分频寄存器的一种缓冲机制也就是这个预分频寄存器实际上是有两个一个是倒数第三行的预分频控制寄存器供我们读写用的并不直接决定分频系数另一个是倒数第二行的预分频缓冲寄存器影子寄存器才是真正起作用的寄存器。 比如我们在某个时刻把预分频寄存器由0改成了1如果在此时立刻改变时钟的分频系数那么就会导致这里在一个计数周期内前半部分和后半部分的频率不一样这里计数计到一半计数频率突然就会改变了这虽然一般并不会有什么问题但是s t m32 的定时器比较严谨设计了这个缓冲寄存器这样当我在计数记到一半的时候改变了分频值这个变化并不会立刻生效而是会等到本次计数周期结束时产生的更新事件预分频计数器的值才会被传递到缓冲寄存器里面去才会生效所以在这里看到即使我在计数中途改变了预分频值计数频率仍然会保持为原来的频率直到本轮技术完成在下一轮计数时改变后的分频值才会起作用。 在定时器结构图中有些寄存器的画法采用了方框下加阴影的方式就说明该寄存器不是只有一个寄存器而是有两个寄存器来形成缓冲机制下图中包括预分频器、自动重装载寄存器、捕获比较寄存器都是的。实际上真正使时序电路状态发生更改的都是影子寄存器。 最后 预分频器内部实际上也是靠计数来分频的当预分频值为零时计数器就一直为零直接输出原频率当预分频值为1时计数器就0 1 0 1 0 1 0 1这样计数再回到零的时候输出一个脉冲这样输出频率就是输入频率的二分频。预分频器的值和时间的分频系数之间有一个数的偏移。 计数器计数频率CK_CNT CK_PSC / (PSC 1)。 3.计数器时序分析 1.计数器工作时序图如下。 内部分频因子为2就是分频系数为2。第一行是内部时钟72MHz第二行是时钟使能高电平启动第三行是计数器时钟因为分频系数为2所以这个频率是上面CK_INT除2然后计数器在这个时钟每个上升沿自增当增到0036时发生溢出之后再来一个上升沿计数器清零产生一个更新事件脉冲另外还会置一个更新中断标志位UIF标志位UIF只要置1就会去申请中断然后中断响应后需要在中断程序中手动清零以上就是计数器的工作流程。 计数器溢出频率CK_CNT_OV CK_CNT / (ARR 1) CK_PSC/ (PSC 1) / (ARR 1) CK_PSC72 MHz 计数器溢出时间1/计数器溢出频率这就是我们计算定时时间溢出时间的式子。 计数器无预装时序图缓冲机制失效 设置APRE 0 那刚才说了预分频器为了防止计数中途更改数值造成错误设计了缓冲寄存器这个计数器也少不了这样的设计并且这个缓冲计算器是用还是不用是可以自己设置的。 计数器无预装时序就是没有缓冲寄存器的情况。 在计数器正在进行自增计数突然更改了自动加载寄存器就是自动重装载寄存器ARR由FF改成了36即计数值的目标值就由FF变成了36所以计数器寄存器计到36之后就直接更新开始下一轮计数。 3.计数器有预装时序缓冲机制有效 APRE 1 计数器有预装时序就是有缓冲寄存器的情况。 通过设置ARPE位就可以选择是否使用预装功能。 在有预装的情况下在计数中途若突然将自动加载寄存器计数目标由F5改成了36下面影子寄存器才是真正起作用的它还是F5所以现在计数的目标还是计到F5产生更新事件同时要更改的36才被传递到影子寄存器在下一个计数周期这个更改的36才有效类似10086本月更改下月生效所以引入影子寄存器的目的实际上是为了同步就是让值的变化和更新事件同步发生防止在运行途中更改造成错误。 在上面这个例子中若不用影子寄存器的话更改TIMx_ARR寄存器的值有一种不严谨情况当F5改到36立即生效但此时计数器已经到了F1已经超过36了F1只能增加但它的目标值却是36比F1小此时计数器寄存器的值只能递增故该寄存器会一直递增到最大值0xFFFF之后回到0x0000再依次递增再加到36才能产生更新。这里就可以看出如果不使用缓冲机制可能会给电路时序的工作造成一些问题。 当然如果你不介意这样的问题的话那就不用管这些细节了毕竟STM32设计出来要考虑到各种各样的情况。 4.RCC时钟树简介 RCC时钟树在STM32中用来产生和配置时钟并且把配置好的时钟发送到各个外设的系统。 时钟是所有外设运行的基础所以时钟是最先配置的东西。在执行主程序之前还会执行一个SystemInit函数这个函数的作用就是配置RCC时钟树。这个结构看上去挺复杂的配置起来还是比较麻烦的不过好在ST公司已经帮我们写好了配置这个时钟数的SystemInit函数。 RCC时钟树可以分为左右两部分时钟产生电路左和时钟分配电路右。中间的SYSCLK就是系统时钟72MHz。 1.时钟产生电路   在时钟产生电路有四个振荡源分别是内部的8MHz高速RC振荡器、外部的4-16MHz高速石英晶体振荡器也就是晶振一般都外接8MHz、外部的32.768kHz低速晶振振荡器一般给RTC提供时钟、内部的40kHz低速RC振荡器给看门狗WDG提供时钟。上面的两个高速晶振是用来提供系统时钟的AHB\APB2\APB1的时钟都是来源于这两个高速晶振。内部和外部都有一个8MHz的晶振都是可以用的只不过外部的石英振荡器比内部的RC振荡器更加稳定所以一般都用外部晶振。如果系统非常简单且不需要过于精确的时钟就可以使用内部的RC振荡器这样可以省下外部的晶振电路。   在SystemInit函数中ST是这样来配置时钟的首先会启动内部的8MHz高速RC振荡器产生时钟选择该时钟为系统时钟暂时以8MHz的内部时钟运行然后再启动外部时钟配置外部时钟信号流经如下图所示的电路 外部晶振信号进入PLLMUL锁相环进行倍频8MHz倍频9倍得到72MHz待锁相环输出稳定后选择锁相环输出为系统时钟。这样就把系统时钟从8MHz切换为了72MHz以上就是ST配置的过程大家可以自己分析一下SystemInit函数。 这样分析之后可以解决实际应用的一个问题那就是如果外部晶振出问题可能会出现程序时钟慢大概10倍的现象。如果外部时钟的硬件电路有问题外部晶振引脚焊接短路或连接错误等系统的时钟就无法切换到72MHz会保持内部的8MHz运行。8M相比于72M大概就慢了10倍。   图中的CSS称为时钟安全系统它同样负责切换时钟。CSS可以监测外部时钟的运行状态一旦外部时钟失效它就会自动把外部时钟切换为内部时钟从而保证程序可以正常运行防止程序卡死造成事故。另外在高级定时器的刹车输入功能中也有CSS一旦CSS检测到外部时钟失效通过或门就会立刻反应到输出控制器让输出控制的电机立刻停止防止意外。即切断输出控制引脚切断电机输出防止发生意外。 2.时钟分配电路   首先系统时钟72MHz进入AHB总线在AHB总线上有一个预分频器在SystemInit函数配置的默认分频系数为1所以AHB总线的时钟自然是72MHz。   之后信号进入APB1总线APB1上同样有预分频器这里SystemInit默认配置的分频系数为2输出为36MHz所以APB1总线的时钟为36MHz但是前面定时器说过所有定时器的时钟都是72MHz这是为什么通用定时器和基本定时器是接在APB1上的但是APB1APB2同理连接定时器还有如图所示的以下结构 通用定时器和基本定时器通过图中APB1下方的支路与APB1连接。由于APB1的预分频系数默认为2则输出到定时器的时钟频率×2。APB2的预分频器的分频系数默认配置为1其他流程与APB1同理。所以基本定时器通用定时器高级定时器的内部基准时钟都是72MHz这样设计为我们使用定时器带来了方便不用考虑不同定时器时钟不同的问题了前提是不乱修改ST提供的SystemInit函数中的默认配置。 在这些时钟输出这里都有一个与门进行输出控制控制位写的是外部时钟使能这就是我们在程序中写RCC_APB2/1PeriphClockCmd作用的地方外设时钟控制作用的地方打开时钟就是在这个位置写1让左边的时钟能够通过与门输出给外设。 那关于时钟数的内容就讲到这里剩下的还有一些给ADC、SDIO等这些提供时钟的电路大家就自己看看了。 参考手册 继续深入学习可查阅参考手册 示例程序定时器定时中断定时器外部时钟 知识点get 前面定时器框图里出现好几个滤波器滤波器工作原理可以滤掉信号的抖动干扰。在一个固定的时钟频率f下进行采样如果连续N个采样点都为相同的电平那就代表输入信号稳定了就把这个采样值输出出去如果这N个采样值不全都相同那就说明信号有抖动这时就保持上一次的输出或者直接输出低电平也行这样就能保证输出信号在一定程度上的滤波这里的采样频率f和采样点数N都是滤波器的参数频率越低采样点数越多滤波效果越好不过相应的信号延迟就越大采样频率f由内部时钟直接而来也可以是由内部时钟加一个时钟分频而来这个分频多少是由参数ClockDivision决定的这个参数其实跟时基单元关系并不大它的可选值可以选择1分频也就是不分频2分频和4分频我们随便配置一个就好了后面代码里使用1分频也就是不分频 。 TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00); 定时器初始化步骤如下 第一步RCC开启时钟定时器的基准时钟和整个外设的工作时钟就都打开了 第二步选择时基单元的时钟源对于定时中断就选择内部时钟源 第三步配置时基单元包括预分频器、自动重装载器、计数模式等这些参数用一个结构体就可配置好了 第四步配置中断输出控制允许更新中断输出到NVIC 第五步配置NVIC在NVIC中打开定时器中断通道并分配一个优先级 第六步运行控制使能一下计数器要不然计数器是不会运行的。当定时器使能后计数器就开始计数了当计数器更新时触发中断 最后再写一个定时器中断函数这样这个中断函数每隔一段时间就能自动执行一次了。 第一步RCC开启时钟这个基本上每个代码都是第一步。在这里打开时钟后定时器的基准时钟和整个外设的工作时钟就都会同时打开了 第二步选择时基单元的时钟源。对于定时中断我们就选择内部时钟源 void TIM_InternalClockConfig(TIM_TypeDef* TIMx);选择内部时钟 void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);选择ITR其他定时器的时钟参数TIMx选择要配置的定时器参数TIM_InputTriggerSource选择要接入哪个其他的定时器 void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);选择TIx捕获通道的时钟参数TIM_TIxExternalCLKSource选择TIx具体的某个引脚还有两个参数是输入的极性和滤波器。 void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);选择ETR通过外部时钟模式1输入的时钟参数TIM_ExtTRGPrescaler外部触发预分频器这里可以对ETR外部时钟再提前做一个分频还有两个参数是输入的极性和滤波器。 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引脚的预分频器、极性、滤波器这些参数 注没选择时钟会默认内部时钟 然后最后一个函数TIM_ETRConfig这个不是用来选择时钟的,就是单独用来配置ETR引脚的预分频器、极性、滤波器这些参数的 涉及函数如下 void TIM_InternalClockConfig(TIM_TypeDef* TIMx) 作用配置TIMx内部时钟 参数说明 第三步配置时基单元。包括这里的预分频器、自动重装器、计数模式等等这些参数用一个结构体就可以配置好了。 涉及函数如下 void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct) 作用根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx时基单元外设。 参数说明 18:49~20:17 如何确定时间参数讲解 假设定时1s也就是定时频率为1Hz那我们就可以PSC给一个7200ARR给一个10000然后两个参数都再减一个1因为预分频器和计数器都有1个数的偏差所以这里要再减个1。然后注意这个PSC和ARR的取值都要在0~65535之间不要超范围了 第四步配置中断输出控制允许更新中断输出到NVIC(开启更新中断到NVIC的通路) 涉及函数如下 void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState) 作用使能/失能TIM中断输出信号。 参数说明 注TIM_IT_Update 更新中断 在STM32库里还提及其它中断源 第五步配置NVIC在NMC中打开定时器中断的通道并分配一个优先级。这部分在上节我们也用过流程基本是一样的 涉及函数 void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct) 第六步就是运行控制了。整个模块配置完成后我们还需要使能一下计数器。要不然计数器是不会运行的。当定时器使能后计数器就会开始计数了当计数器更新时触发中断。 涉及函数如下 void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState) 作用启用或禁用指定的TIM外设。 参数说明 这样初始化基本上就OK了,接下来我们再看几个函数,因为在初始化结构体里有很多关键的参数,比如自动重装值和预分频值等等,这些参数可能会在初始化之后还需要更改,如果为了改某个参数还要再调用一次初始化函数那太麻烦了。所所以这里有一些单独的函数可以方便地更改这些关键参数。 比如这里的TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode)就是用来单独写预分频值的,看一下参数Prescaler就是要写入的预分频值后面还有个参数PSCReloadMode写入的模式。我们上一小节说了预分频器有一个缓冲器写入的值是在更新事件发生后才有效的所以这里有个写入的模式可以选择是听从安排在更新事件生效或者是在写入后手动产生一个更新事件让这个值立刻生效。 TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);用来改变计数器的计数模式,参数CounterMode选择新的计数器模式。 TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);自动重装器预装功能配置前面讲过直接配置使能或失能就可以。 TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);,给计数器写入一个值。如果你想手动给一个计数值就可以用这个函数 TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);给自动重装器写入一个值,如果你想手动给一个自动重装值就可以用这个函数 uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);获取当前计数器的值,如果你想看当前计数器计到哪里了就可以调用一下这个函数,返回值就是当前的计数器的值 uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);获取当前的预分频器的值 最后我们再写一个定时器的中断函数。这样这个中断函数每隔一段时间就能自动执行一次了。 本次实验要完成的现象是定义一个 uint16_t 的 Num 变量使其每秒1。 Timer.c #include stm32f10x.h // Device header/* 定时器初始化 对应定时中断结构图第一步RCC开启时钟定时器的基准时钟和整个外设的工作时钟就都打开了 第二步选择时基单元的时钟源对于定时中断就选择内部时钟源 第三步配置时基单元包括预分频器、自动重装载器、计数模式等参数用结构体配置 第四步配置输出中断控制允许更新中断输出到NVIC 第五步配置NVIC在NVIC中打开定时器中断通道并分配一个优先级 第六步运行控制使能计数器当定时器使能后计数器就开始计数了当计数器更新时触发中断 最后再写一个中断函数中断函数每隔一段时间就能自动执行一次*/ void Timer_Init(void) //定时中断初始化代码 {//初始化tim2也就是通用定时器//使用APB1的开启时钟函数TIM2是APB1总线的外设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; //计数器模式向上计数/*定时1s也就是定时频率为1Hz定时频率72M/ (PSC 1) / (ARR 1) 1s 1Hz那就可以PSC给7200ARR给100001MHz等于10^6Hz然后两个参数再减1在这里预分频是对72M进行7200分频得到的就是10k的计数频率在10k的频率下计10000个数就是1s的时间*/TIM_TimeBaseInitStructure.TIM_Period 10000 - 1; //ARR自动重装载寄存器的值TIM_TimeBaseInitStructure.TIM_Prescaler 7200 - 1; //PSC预分频器的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器的值高级定时器里用到这里用不到直接给0TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);/*查看库函数TIM_TimeBaseInit源码的最后发现函数内会立刻生成一个更新事件来重新装载预分频器和重复计数器的值预分频器有缓冲寄存器我们写入的PSC和ARR只有在更新事件时才会起作用为了让写入的值立刻起作用故在函数的最后手动生成了一个更新事件但是更新事件和更新中断是同时发生的更新中断会置更新中断标志位手动生成一个更新事件就相当于在初始化时立刻进入更新函数执行一次在开启中断之前手动清除一次更新中断标志位就可以避免刚初始化完成就进入中断函数的问题(避免出现上电后OLED从1开始计数)*/TIM_ClearFlag(TIM2,TIM_FLAG_Update);//使能中断到NVIC的通路TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启更新中断到NVIC的通道//NVIC中断NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC优先级分组NVIC_InitTypeDef NVIC_InitTyStructure;NVIC_InitTyStructure.NVIC_IRQChannel TIM2_IRQn;// 定时器2在NVIC里的通道NVIC_InitTyStructure.NVIC_IRQChannelCmd ENABLE;NVIC_InitTyStructure.NVIC_IRQChannelPreemptionPriority 2;//抢占优先级NVIC_InitTyStructure.NVIC_IRQChannelSubPriority 1;//响应优先级NVIC_Init(NVIC_InitTyStructure); //启动定时器TIM_Cmd(TIM2,ENABLE);//当定时器产生更新时就会触发中断 } /* 中断函数模版 void TIM2_IRQHandler(void) //当定时器产生更新中断时这个函数就会自动被执行 {//检查中断标志位if(TIM_GetITStatus(TIM2,TIM_IT_Update) SET){//执行相应的用户代码Num ;TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位} } */ main.c #include stm32f10x.h // Device header #include Delay.h #include OLED.h #include Timer.huint16_t Num;int main(void) {OLED_Init(); //初始化OLEDTimer_Init(); //初始化定时器OLED_ShowString(1,1,Num:);while(1){OLED_ShowNum(1,5,Num,5);OLED_ShowNum(2,5,TIM_GetCounter(TIM2),5);//CNT计数器值的变化情况变化范围是ARR从0一直到自动重装值10000-1}}//定时器2中断函数放在使用中断的main.c文件中在startup文件中 void TIM2_IRQHandler(void) //当定时器产生更新中断时这个函数就会自动被执行 {//检查中断标志位if(TIM_GetITStatus(TIM2,TIM_IT_Update) SET){//执行相应的用户代码Num ;//定时器每秒自动加一个Num全局变量TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位}}定时器外部时钟选择实验 可以在引脚定义图里找TIMx的ETR引脚是哪个 在上一个定时中断实例程序基础上进行更改基本任务仍然是定时中断时钟部分就不使用内部时钟了 对射式红外传感器DO数字输出接到PA0引脚这个PA0引脚就是TIM2的ETR引脚我们就在这个引脚输入一个外部时钟。 本次实验要完成的现象是用光敏传感器手动模拟一个外部时钟定义一个 uint16_t 的 Num 变量当外部时钟触发10次预分频之后的脉冲后Num 1。器件连接图和程序源码如下所示 提示 这里手册里推荐配置是浮空输入但是我一般不太喜欢浮空输入因为一旦悬空电平就会跳个没完所以我准备给上拉输入这也是可以的。 那什么时候需要用浮空输入呢就是如果你外部的输入信号功率很小内部的这个上拉电阻可能会影响到这个输入信号这时就可以用一下浮空输入防止影响外部输入的电平。 在6-1的基础上更改尤其注意在第二步更改时基单元的时钟源通过ETR引脚的外部时钟模式2配置。 void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter) 作用配置TIMx外部时钟模式2 参数说明 Timer.c #include stm32f10x.h // Device header/* 定时器初始化 对应定时中断结构图第一步RCC开启时钟定时器的基准时钟和整个外设的工作时钟就都打开了 第二步选择时基单元的时钟源对于定时中断就选择内部时钟源 第三步配置时基单元包括预分频器、自动重装载器、计数模式等参数用结构体配置 第四步配置输出中断控制允许更新中断输出到NVIC 第五步配置NVIC在NVIC中打开定时器中断通道并分配一个优先级 第六步运行控制使能计数器当定时器使能后计数器就开始计数了当计数器更新时触发中断 最后再写一个中断函数中断函数每隔一段时间就能自动执行一次*/ void Timer_Init(void) //定时中断初始化代码 {//初始化tim2也就是通用定时器//使用APB1的开启时钟函数TIM2是APB1总线的外设RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//外部模式2需要用到gpio,进行GPIOA的时钟配置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,0x00);//外部触发滤波器范围0X00-0X0F这里不用滤波了//配置时基单元 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; //指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; //计数器模式/*定时1s也就是定时频率为1Hz定时频率72M/ (PSC 1) / (ARR 1) 1s 1Hz那就可以PSC给7200ARR给100001MHz等于10^6Hz然后两个参数再减1在这里预分频是对72M进行7200分频得到的就是10k的计数频率在10k的频率下计10000个数就是1s的时间*/TIM_TimeBaseInitStructure.TIM_Period 10 - 1; //ARR 自动重装载记到9 这两个值改小我们手动模拟的没那么快TIM_TimeBaseInitStructure.TIM_Prescaler 1 - 1; //PSC 不分频TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器的值TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);/*在TIM_TimeBaseInit函数的最后会立刻生成一个更新事件来重新装载预分频器和重复计数器的值预分频器有缓冲寄存器我们写入的PSC和ARR只有在更新事件时才会起作用为了让写入的值立刻起作用故在函数的最后手动生成了一个更新事件但是更新事件和更新中断是同时发生的更新中断会置更新中断标志位手动生成一个更新事件就相当于在初始化时立刻进入更新函数执行一次在开启中断之前手动清除一次更新中断标志位就可以避免刚初始化完成就进入中断函数的问题*/TIM_ClearFlag(TIM2,TIM_FLAG_Update);//使能中断TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启更新中断到NVIC的通道//NVIC中断NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC优先级分组NVIC_InitTypeDef NVIC_InitTyStructure;NVIC_InitTyStructure.NVIC_IRQChannel TIM2_IRQn;NVIC_InitTyStructure.NVIC_IRQChannelCmd ENABLE;NVIC_InitTyStructure.NVIC_IRQChannelPreemptionPriority 2;//抢占优先级NVIC_InitTyStructure.NVIC_IRQChannelSubPriority 1;//响应优先级NVIC_Init(NVIC_InitTyStructure); //启动定时器TIM_Cmd(TIM2,ENABLE);//当产生更新时就会触发中断 }//函数封装 uint16_t Timer_getcounter(void) {return TIM_GetCounter(TIM2); }/* 中断函数模版 void TIM2_IRQHandler(void) //当定时器产生更新中断时这个函数就会自动被执行 {//检查中断标志位if(TIM_GetITStatus(TIM2,TIM_IT_Update) SET){//执行相应的用户代码Num ;TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位} } */main.c #include stm32f10x.h // Device header #include Delay.h #include OLED.h #include Timer.huint16_t Num;int main(void) {OLED_Init(); //初始化OLEDTimer_Init(); //初始化定时器OLED_ShowString(1,1,Num:);OLED_ShowString(2,1,CNT:);while(1){OLED_ShowNum(1,5,Num,5);OLED_ShowNum(2,5,TIM_GetCounter(TIM2),5);//CNT计数器值的变化情况变化范围是ARR从0一直到自动重装值10000-1}}//定时器2中断函数放在使用中断的main.c文件中在startup文件中 void TIM2_IRQHandler(void) //当定时器产生更新中断时这个函数就会自动被执行 {//检查中断标志位if(TIM_GetITStatus(TIM2,TIM_IT_Update) SET){//执行相应的用户代码Num ;//定时器每秒自动加一个Num全局变量TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位}}上面这里是number下面是CNT计数器的值我们用挡光片挡一下CNT就加一因为现在时基单元没有预分频所以每次遮挡CNT都会加一。如果有预分频呢就是遮挡几次才能加一次然后加到九后自动清零同时申请中断number这就是第二个程序的现象了。 TIM输出比较 输出比较功能简介 输出比较英文全称Output Compare简称OC。它主要的功能是可以通过比较计数器CNT和捕获/比较寄存器Capture/Compare RegisterCCR值的关系来输出电平进行置1、置0的翻转操作用于输出一定频率和占空比的PWM波形。 IC全称是Input Capture意为输入捕获CC全称是Capture Compare意为输入捕获和输出比较(捕获比较)的单元 捕获/比较寄存器是输入捕获和输出比较共用的,当使用输入捕获时它就是捕获寄存器当使用输出比较时它就是比较寄存器。那在输出比较这里这块电路会比较CNT和CCR的值CNT计数自增CCR是我们给定的一个值当CNT大于CCR、小于CCR或者等于CCR时这里输出就会对应的置1、置0、置1、置0这样就可以输出一个电平不断跳变的PWM波形了。这就是输出比较的基本功能。 每个高级定时器和通用定时器都拥有4个输出比较的通道可以同时输出4路PWM波形且高级定时器的前3个通道额外拥有死区生成电路和互补输出的功能用于驱动三相无刷电机本课程暂未涉及我们主要学习通用定时器的输出比较功能。4个输出比较通道都有独立的CCR寄存器但是它们共用同一个CNT计数器。 PWM简介 PWMPulse Width Modulation即脉冲宽度调制PWM波形是一个数字输出信号是由高低电平组成的是一种对模拟电平信号进行数字编码的方法。在具有惯性的系统中可以通过对一系列脉冲的宽度进行调制来等效地获得所需要的模拟参量常应用于电机控速等领域。也就是说使用PWM波形是用来等效地实现一个模拟信号的输出。例如led呼吸灯电机调速如下解释 以LED为例GPIO的输出信号只能是数字信号按理说LED只能有完全亮和完全灭两种状态如果想通过数字信号输出模拟量可以通过以下的方法实现让LED不断点亮、熄灭、点亮、熄灭当点亮、熄灭的频率足够大时由于LED的余晖和人眼的视觉暂留效应LED就不会闪烁了就会呈现出一个中等亮度。当调控点亮和熄灭的时间比例时就能让LED呈现出不同的亮度级别。 对于电机调速也类似在高频率下不断让电机交替通断由于电机断电后不会立刻停止而是由于惯性转动后停下电机的速度就能维持在一个中等速度。 PWM的秘诀是天下武功唯快不破 只要我闪的足够快你就发现不了我到底是闪着亮的还是一个正常的平稳的亮度。需要注意的是只有在具有惯性的系统中才能用PWM对模拟信号进行编码。 从下图可以看出高低电平跳变的数字信号可以被等效地表示为中间虚线所表示的模拟量。当上面电平时间长一点下面电平短一点的时候等效地模拟量就偏向于上面当下面电平时间长一点上面电平时间短一点的时候等效地模拟量就偏向于下面。也就是说占空比越大等效的模拟量就越趋近于数字量的高电平占空比越小等效的模拟量就越趋近于数字量的低电平且这个等效关系一般而言是线性一一对应的比如高电平是5V低电平是0V那50%占空比就等效于中间电压就是2.5V20%占空比就等效于1/5处的电压就是1V这就是占空比。 使用PWM波形就可以在数字系统等效输出模拟量就能实现LED控制亮度、电机控速等功能了。 PWM参数 首先明白Ts就是下图这里Ts代表一个高低电平变换周期的时间 在使用PWM对模拟量进行编码时以下三个参数尤其重要 频率 f 1 / Ts周期的倒数就是频率变换越快频率越大PWM的频率越快它等效模拟的信号就越平稳不过同时性能开销就越大一般来说PWM的频率在几kHz到几十kHz之间。 占空比qTon/Ts( Ton是高电平的时间Ts是一个周期的时间。q就是高电平时间相对于整个周期时间的比例占空比决定了PWM等效出的模拟电压的大小。一般用百分比表示。 分辨率占空比的变化步距比如有的占空比只能是1%、2%、3%等等这样以1%的布距跳变那它的分辨率就是1%如果可以1.1%、1.2%、1.3%等等这样以0.1%的布距跳变那它的分辨率就是0.1%。所以分辨率就是占空比变化的细腻程度。分辨率的大小要看实际项目的需求定。如果既要高频率又要高分辨率就需要硬件电路要有足够的性能。要求不高的情况下1%的分辨率就足够使用了。 输出比较通道通用定时器 那接下来我们就来具体地分析一下定时器的输出比较模块是怎么来输出PWM波形的我们先看一下通用定时器的这个结构。 左边是CNT和CCR比较的结果右边就是输出比较电路最后通过TIM_CH1输出的GPIO引脚上然后下面还有三个同样的单元分别输出到CH2、CH3、CH4。图的左边是CNT计数器和CCR1第一路的捕获/比较寄存器它俩进行比较当CNT CCR1或者CNT CCR1时输出模式控制器就会收到一个信号输出模式控制器就会改变它输出的OC1REF的高低电平。REF是Reference的缩写意为参考信号。上面有个ETRF输入是定时器的一个小功能一般不用不需要了解 接下来OC1REF信号兵分两路一路将REF信号映射到主模式控制器的TRGO输出上去去触发其他外设的功能不过REF的主要去向还是下面这一路通往一个极性选择电路通过控制TIMx_CCER寄存器的值0或1可以选择是否将REF信号翻转写0信号就会往上走就是信号电平不翻转进来啥样出去还是啥样写1信号就会往下走就是信号通过一个非门取反输出的信号就是输入信号高低电平反转的信号这就是极性选择就是选择是不是要把高低电平反转一下之后通往输出使能电路可以控制是否输出最后通往OC1引脚即TIMx_CH1通道的引脚在引脚定义表中即可找到具体的GPIO口。 补充 极性选择电路 非门取反 输出模式控制器的执行逻辑工作流程 接下来我们还需要看一下这个输出模式控制器它具体是怎么工作的。什么时候给REF高电平什么时候给REF低电平。我们看一下下面的这个表这就是输出比较的8种模式也就是这个输出模式控制器里面的执行逻辑。这个模式控制器的输入是CNT和CCR的大小关系输出是REF的高低电平里面可以选择多种模式来更加灵活地控制REF输出。这个模式可以通过寄存器来进行配置具体操作看下面的表 冻结CNT CCR时REF维持原状态实际上此时REF与CNT和CCR都无关不管谁大谁小即CNT和CCR无效REF直接保持为上一个状态。这有什么用呢比如你正在输出PWM波突然想暂停一会儿输出就可以设置成这个模式一但切换为冻结模式后输出就暂停了并且高低电平也维持为暂停时刻的状态保持不变。这就是冻结模式的作用 无效/有效电平这个有效电平和无效电平一般是高级定时器里面的一个说法是和关断、刹车这些功能配合表述的他说的比较严谨所以叫有效电平和无效电平在这里为了理解方便你可以直接认为置有效电平就是置高电平。 匹配时… 这三个模式都是当CNT与CCR值相等时执行操作。 第一个是CNT等于CCR时REF置有效电平也就是高电平第二个是相等时置无效电平也就是低电平第三个是相等时电平翻转。这些模式就可以用做波形输出了比如相等时电平翻转这个模式这个可以方便地输出一个频率可调占空比始终为50%的PWM波形。比如你设置CCR为0那CNT每次更新清0时就会产生一次CNTCCR的事件这就会导致输出电平翻转一次每更新两次输出为一个周期并且高电平和低电平的时间是始终相等的也就是占空比始终为50%当你改变定时器更新频率时输出波形的频率也会随之改变。它俩的关系是输出波形的频率更新频率/2因为更新两次输出才为一个周期。这就是匹配时电平翻转模式的用途。 那上面这两个相等时置高电平和低电平感觉用途并不是很大因为它们都只是一次性的置完高或低电平后就不管事了所以这俩模式不适合输出连续变化的波形。如果你想定时输出一个一次性的信号那可以考虑一下下这两个模式。 强制为无效电平|有效电平 这两个模式是CNT与CCR无效REF强制为无效电平或者强制为有效电平这两个与冻结模式类似。如果你想暂停波形输出并且在暂停期间保持低电平或者高电平那你就可以设置这两个强制输出模式。PWM模式1/2 它们可以用于输出频率和占空比都可调的PWM波形也是我们主要使用的模式。这个情况比较多一般我们都只使用向上计数向上/向下计数之间也只有大小关系、极性不用基本思想都是一样的。PWM模式1/2的向上计数区别就是输出的高低电平反过来了PWM模式2实际上就是PWM模式1输出的取反(改变PWM模式1和PWM模式2就只是改变了REF电平的极性而已REF输出之后还有一个极性的配置这里设计的比较灵活输出模式里可以设置极性最终输出之前也可以设置极性所以使用PWM模式1的正极性和PWM模式2的反极性最终的输出是一样的。所以使用的话我们可以只使用PWM模式1并且是向上计数这一种模式就行了。 输出PWM波形及参数计算 那PWM模式1向上计数是怎么输出频率和占空比都可调的PWM波形的呢 以PWM模式1、向上计数模式为例PWM波形产生原理输出PWM的基本结构如下图所示 在上图中首先左上角是时基单元和运行控制部分再左边是时钟源选择省略上一小节内容在这里我们不需要使用更新事件的中断申请输出PWM暂时还不需要中断这就是时基单元的部分。配置好了时基单元这里的CNT就可以开始不断地自增运行了。然后下面粉红区域就是输出比较单元了总共有四路输出比较单元的最开始是CCR捕获/比较寄存器CCR是我们自己设定的CNT不断自增运行同时它俩还在不断进行比较CCR捕获/比较寄存器后面是输出模式控制器在这里以PWM模式1为例是PWM模式1的执行逻辑那它是怎么输出PWM波形的解释如下右上角图中蓝色线是CNT的值黄色线是ARR的值蓝色线从0开始自增一直增到ARR也就是99之后清0继续自增在这个过程中红色线是CCR比如设置CCR为30执行输出模式控制器里的逻辑下面的绿色线就是输出可以看到CNTCCR时置高电平之后CNTCCR就变为低电平当CNT溢出清0后CNT又小于CCR所以置高电平…这样一直持续下去REF的电平就会不断变化并且它的占空比是受CCR的值的调控的如果CCR的值设置的高一些输出的占空比就会变大CCR设置的低一点输出的占空比就会变小以上就是PWM的工作流程。这里REF就是一个频率可调占空比也可调的PWM波形最终再经过极性选择输出使能最终通向GPIO口这样就能完成PWM波形的输出了。需要注意的是 设置的CCR值越接近ARR输出的PWM波形的占空比就越大。 我们就再来看一下PWM的参数是如何计算的 PWM的一个周期如上图中的下面绿色区段的红线区间可以看出它始终对应着计数器的一个溢出更新周期所以PWM的频率就等于计数器的更新频率 当CNT CCR时电路已经置为低电平故REF为高电平的时间为CNT从0变到2930个数的时间。 CCR的值应设置在0到ARR1的范围里CCRARR1时占空比是100%ARR越大CCR的范围就越大对应的分辨率就越大 参数计算公式如下所示 PWM频率即计数器的更新频率 Freq CK_PSC / (PSC 1) / (ARR 1) PWM占空比Duty CCR / (ARR 1) PWM分辨率即占空比变化的步距 Reso 1 / (ARR 1)以上定义的分辨率是占空比最小的变化步距。ARR越大CCR的变化范围就越大分辨率就越高。占空比变化的越细腻越好 输出比较通道高级定时器 这个电路仅作了解即可不需掌握。 从这里画一个圈把这个圈里面的东西去掉那它就跟通用定时器的电路是一样的了那这个圈里的电路到底有什么用呢这就需要和外部电路来一起理解了在它外面通常要接一个这样的电路上面是正极接着是一个大功率开关管一般都是MOS管就是一种大功率电子开关然后再来一个MOS管最后到GND左边是控制机比如说给高电平右边两根线就导通低电平就断开下面也是一样有一个控制极高电平导通低电平断开这就是一个基本的推挽电路中间是输出如果上管导通下管断开那输出就是高电平如果下管导通上管断开那输出就是低电平如果上下管都导通 那就是电源短路这样是不允许的。如果上下管都断开那输出就是高阻态这就是推挽电路工作流程如果有两个这样的推挽电路那就构成了H桥电路就可以控制直流电机正反转了如果有三个这样的推挽电路那就可以用于驱动三相无刷电机了这就是这个电路的用途。 那对于这个电路来说如果直接用单片机来控制的话那就需要两个控制极并且这两个控制极电平是相反的也就是互补因为上管导通下管就必须断开下管导通上管就必须断开知道了外围电路的需求再来理解内部电路结构那自然就好理解了。 首先这个OC1和OC1N就是两个互补的输出端口分别控制上管和下管的导通和关闭然后是在切换上下管导通状态时如果在上管关断的瞬间下管立刻就打开那可能会因为器件的不理想上管还没有完全关断下管就已经导通了出现了短暂的上下管同时导通的现象这会导致功率损耗啊 引起器件发热所以在这里为了避免这个问题就有了死区生成电路它会在上管关闭的时候延迟一小段时间再导通下管下管关闭的时候延迟一小段时间再导通上管这样就可以避免上下管同时导致的现象了这就是死区生成和互补输出的用途。 舵机和直流电机 舵机 舵机是小型直流伺服电机的一种是一种根据输入PWM信号占空比来控制舵机输出轴的角度的装置。它有三根输入线其中两根是电源线一根是PWM信号输入线。白色输出轴会固定在一个指定的角度不动固定的位置是由信号线的PWM信号来决定的这就是舵机的工作方式。 上边右图中可以看出舵机其实并不是一种单独的电机可以发现它是由一个直流电机、一个减速齿轮组、一个电位器电压编码器和一个控制板 4部分组成的整体。舵机不是一种单独的电机它的内部是由直流电机驱动的。内部的控制电路板是一个电机的控制系统整个舵机内部形成了一个闭环的控制系统。 PWM信号输入到控制板给控制板一个指定的目标角度然后这个电位器检测输出轴的当前角度如果大于目标角度电机就会反转如果小于目标角度电机就会正转最终使输出轴固定在指定的角度这就是舵机的内部工作流程简而言之输入一个PWM波形输出轴固定在一个角度。 “伺服”—词源于希腊语“奴隶”的意思英文为Servo。人们想把某一个结构或系统当作一个得心应手的驯服工具服从控制信号的要求而动作。伺服的主要任务是按照控制命令的要求对输出信号和输出功率进行放大、变换与调控等处理使驱动装置输出的力矩、速度和位置控制得非常灵活方便。由于它的“伺服”性能因此而得名——伺服系统。它的优势在于可以非常灵活地控制输出装置的力矩、速度和位置等物理参量。   交流伺服电机和直流伺服电机的共同点是利用传感器编码器对转子的位置、转速、力矩、转向进行检测斌且将得到的信号经由伺服驱动器反馈给伺服控制器从而达到调节转子位置、转速、力矩、转向的目的 二者的不同点在于一般而言交流伺服电机相较于直流伺服电机对转子有更高的控制精度。 输入信号脉冲宽度周期是20ms也就是一个上升沿到下一个上升沿之间的时间是20ms。 舵机对输入的PWM信号的要求如下周期为20ms对应50Hz高电平宽度为0.5 ~ 2.5ms就是占空比是这个范围对应的输出角度如上图。这时一个180° 的舵机输出轴的角度是-90° 到90° 或者你规定是0° 到180°输入信号脉冲宽度与舵机输出轴转角的对应关系都是线性一一对应的(图中高电平的时间如果是0.5ms对应的角度就是-90度如果是1ms对应的角度就是-45度等)给个PWM输出轴就会固定在一个角度。实际应用中比如机器人、机械臂可以用舵机来控制关节遥控车、遥控船可以用舵机来控制方向。这里的PWM波形实际上是作为一个通信协议来使用的与用PWM波形等效出一个模拟输出的关系不大(前面讲了等效模拟必须是以惯性连续为前提)将PWM当成一个通信协议也是一个比较常见的应用因为很多控制器都有PWM输出的功能而且PWM只需要一根信号线就行了这也是一种应用形式。 接下来看一下舵机的硬件电路上图第一个是引脚定义图在舵机上有三根线分别是黑电源负极GND、红电源正极5V、黄PWM信号线。上图第二个图中在实际应用中GND就接GND电源5V是电机的驱动电源一般电机都是大功率设备它的驱动电源也必须是一个大功率的输出设备如果能像这样单独提供供电那就再好不过了如果不能那也要注意电源的功率是不是能达标如果单独供电的话供电的负极要和STM32 共地然后正极接在5伏供电引脚上对于我们套件的话可以直接从STLINK的5v输出脚引一根线使用USB的5V供电也是带得动的信号线PWM就直接接到STM32引脚上就行了舵机内部是有驱动电路的PWM只是一个通信线是不需要大功率的。 直流电机及驱动 可以用PWM来控制电机的速度。直流电机是一个单独的电机里面是没有驱动电路的所以我们就要外挂一个驱动电路来控制。直流电机是一种能将电能转换为机械能的装置。有两个电极当电极正接时电机正转当电极反接时电机反转。上图最左所示的电机是130直流电机(本实验)。 直流电机属于大功率器件GPIO口无法直接驱动需要配合电机驱动电路来操作。本课程使用TB6612电机驱动芯片来驱动电机。 电机驱动电路同样也是一个研究课题市面上也有很多的电机驱动可供选择常见的电机驱动芯片有TB6612、DRV8833、L9110、L298N等另外还有用分立元件MOS管搭建的驱动电路它可以实现更大的驱动功率。当然也可以自己用MOS管设计电路。 本课程就直接使用TB6612这款电机驱动芯片来驱动电机的使用现成的芯片还是挺方便的别人都把电路设计好集成在芯片里了我们直接拿来用就行了。TB6612是一款双路H桥型的直流电机驱动芯片其中有两个驱动电路可以独立地驱动两个直流电机并且控制其转速和方向。如上图中间是电机驱动板芯片是TB6612外围电路只需三个滤波电容就行了可见这个芯片的集成度还是非常高的基本上不需要我们在加什么东西。如上图右侧是H桥电路的基本结构是由两路推挽电路组成的比如左边上管导通下管断开那左边输出就是接在VM的电机电源正极下管导通上管断开那就是接在PGND的电源负极如果有两路推挽电路中间接一个电机左上和右下导通电流就是从左流向右右上和左下导通电流的方向就反过来从右边流向左边H桥可以控制电流流过的方向所以它能控制电机的正反转。大概了解一下就行应用的话不用管那么多。 TB6612电机驱动模块的连接电路图和引脚定义图如下所示 如上图 VM就是电机电源的正极和舵机的电源要求是一样的要接一个可以输出大电流的电源电压和电机的额定电压保持一致比如时5v的电机就接5v电压。VCC是逻辑电平输入端一般和控制器的电源保持一致。比如使用STM32是3.3v的器件就接3.3v如果是51单片机是5V的器件那就接5V这个引脚并不需要大功率所以可以和控制器共用一个电源。第三个引脚是GND这个就接系统的负极就行了。然后板子在右边还有两个GND这个都是一样的引脚在板子内部都是连通的随便选一个GND用就可以。AO1\AO2\BO1\BO2是两路电机的输出可以分别接两个电机。AO1和AO2就是A路的两个输出它的控制端是上面的三个PWMA、AIN1和AIN2这三个引脚控制下面的A路的一个电机对应关系如上图的灰色填充其中PWMA引脚要接PWM信号输出脚PA0AIN1和AIN2引脚可以任意接两个普通的GPIO口这三个引脚给一个低功率的控制信号驱动电路就会从VM汲取电流来输出到电机这样就能完成低功率的控制信号控制大功率设备的目的。右边的BO1及BO2这一路也是和A路的功能和操作方法是完全一样的。STBY引脚意为Stand By为待机控制引脚。如果接GND芯片就不工作处于待机状态。如果接到逻辑高电平VCC3.3V芯片就正常工作。如果不需要待机模式的话可以直接接VCC3.3v如果需要控制可以接入任意一个GPIO口给高低电平就可以进行控制。 PWMA、AIN1和AIN2三个引脚是怎么控制电机正反转和速度如上图下面的表 STBY低电平就待机高电平就正常工作。如果IN1和IN2全接高电平两个输出O1和O2都为低电平这样两个输出就没有电压差电机是不会转的制动全高或全低电机都不会转如果IN1和IN2全接低电平两个输出O1和O2直接关闭电机也是不会转的如果IN1给低电平、IN2接高电平电机就是处于反转状态电机就是处于反转状态那转还是不转呢要取决于PWM如果PWM给高电平那输出就是一低一高有电压差了电机可以转这时候定义的是反转开始转了如果PWM给低电平那输出两个低电平电机还是不转制动这就是反转的逻辑IN1给低IN2给高PWM高转低不转。如果PWM是一个不断翻转的电平信号那电机就是快速的反转、停止、反转、停止如果PWM频率足够快电机就可以连续稳定的反转并且速度取决于PWM信号的占空比这就是反转的工作流程。在这里的PWM就是使用PWM来等效一个模拟量的功能。正转和上面差不多。IN1给高IN2给低PWM高正转、低不转。如果PWM频率足够快电机就是连续稳定地正转了并且速度取决于PWM信号的占空比。 参考手册 强制输出模式就是CNT和CCR无效REF强制为高和低的那两种模式 输出比较模式就是CNT等于CCR时REF是冻结、置高、置低、还是翻转的那四种模式 最后PWM这个比较重要就是CNT大于或小于CCRREF置高或置低的那两种模式。 更多细节问题大家可以再详细研究一下手册另外看这个手册的时候可能他的语言会比较晦涩难懂而且它这里面经常会像这样引用一大堆寄存器的名字所以看的时候还要多对照寄存器介绍来理解这些寄存器的解释也都是很重要的内容把功能描述和寄存器描述都对照看看这样才好懂一些。 示例程序PWM驱动LED呼吸灯PWM驱动舵机PWM驱动直流电机 PWM驱动LED呼吸灯 现象在PA0端口接入LEDLED在不断地变换亮度实现了一个呼吸灯的效果。 接线图如下注意LED是正极接在PA0引脚负极接在GND的驱动方法这样就是高电平点亮低电平熄灭这是正极性的驱动方法这样的话观察更直观一点就是占空比越大LED越亮占空比越小LED越暗 。 上一小节我们已经把pwm的整个通路都讲完了现在我们只需要把这些模块都打通那就可以输出PWM了。 pwm初始化函数基本步骤 第一步RCC开启时钟把要用的TIM外设和GPIO外设的时钟打开 第二步配置时基单元包括时钟源选择和时基单元都配置好 第三步配置输出比较单元,里面包括这个CCR的值、输出比较模式、极性选择、输出使能这些参数这些参数在库函数里也是用结构体统一来配置的 第四步配置GPIO把PWM对应的GPIO口初始化为复用推挽输出的配置PWM和GPIO的对应关系可以参考引脚定义表 第五步运行控制启动计数器这样就能输出PWM了 OC输出比较相关库函数 1.OC(输出比较)初始化 // 配置输出比较模块输出比较单元有四个对应也有四个函数 // 第一个参数选择定时器 第二个参数是结构体就是输出比较的一些参数 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_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);2.OC参数更改运行时候更改参数的函数TIM_SetComparex函数最重要其他的了解即可 // 单独设置输出比较的极性带N的是高级定时器中互补通道的配置OC4没有互补通道所以就没有OC4N的函数 // 在这里可以设置输出极性在OC初始化函数中也可以用结构体设置输出极性这里相当于将单独修改结构体中的某一参数封装到一个函数中 // 在上面结构体初始化的那个函数里也可以设置极性这两个地方设置极性的作用是一样的只不过是用结构体是一起初始化的在这里是单独函数进行修改的 // 一般来说结构体里的参数都会有一个单独的函数可进行更改 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);// 单独更改CCR寄存器值的函数 //在运行时更改占空比就需要用到这四个函数 void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1); 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);3.OC输出比较的一些小功能不常用了解即可 // 配置强制输出模式运行中想要暂停输出波形且强制输出高/低电平 // 强制输出高电平和设置100%占空比等效强制输出低电平和设置0%占空比等效 void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction); void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction); void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction); void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);// 配置CCR寄存器的预装功能影子寄存器就是写入的值不会立即生效而是在更新事件才会生效可以避免一些小问题 void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload); void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload); void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload); void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);// 配置快速使能手册中“单脉冲模式”一节有介绍 void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast); void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast); void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast); void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);// 清除REF信号手册中在“外部事件时清除REF信号”一节有介绍 void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear); void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear); void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear); void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);4.补充 //仅高级定时器使用 //在使用高级定时器输出PWM时。需要调用这个函数使能输出。否则PWM将不能正常输出 void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);5.补充 //TIM_OCMode 输出比较模式中的选择TIM_OCMode_Timing//冻结模式 TIM_OCMode_Active//相等时置有效电平 TIM_OCMode_Inactive//相等时置无效电平 TIM_OCMode_Toggle//相等时电平翻转 TIM_OCMode_PWM1//PWM模式1主要用 TIM_OCMode_PWM2//PWM模式2 TIM_ForcedAction_Active//强制输出模式初始化时不使用 TIM_ForcedAction_InActive TIM_Output_Compare_Polarity 输出比较的极性选择TIM_OCPolarity_High //高极性就是极性不翻转REF波形直接输出或者说是有效电平是高电平REF有效时输出高电平 TIM_OCPolarity_Low //低极性就是REF电平取反或者说是有效电平为低电平第一步RCC开启时钟把我们要用的TIM外设和GPIO外设的时钟打开 第二步配置时基单元 第三步配置输出比较单元,里面包括这个CCR的值、输出比较模式、极性选择、输出使能这些参数这些参数在库函数里也是用结构体统一来配置的。 涉及函数 void TIM_OCXInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct)其中TIM_OCXInit的X为1、2、3、4对应4个输出比较单元或者说输出比较通道。你需要初始化哪个通道就调用哪个函数。不同的通道对应的GPIO口也是不一样的所以这里要按照你GPIO口的需求来。这里使用的是PAO口对应的就是第一个输出比较通道。对于TIM2来说就是下图对应引脚 最终这个PWM波形肯定是要借用一下GPIO口才能输出那这个STM32的OC1通道是借了哪个GPIO口呢我们可以打开这个引脚定义表默认复用功能这一列就是片上外设的端口和GPIO的连接关系在这里可以看到有TIM2_CH1_ETR,它是在PA0这一行的这就说明TIM2的ETR引脚和通道1的引脚都是借用了PA0这个引脚的位置的换句话说就是TIM2的引脚复用在了PA0引脚上所以说如果我们要使用TIM2的OC1也就是CH1通道输出PWM那它就只能在PA0的引脚上输出而不能任意选择引脚输出这个关系是定死的不能任意更改。 你要使用哪个外设就只能用对应的引脚不过但是虽然它是定死的STM32还是给了我们一次更改的机会的这就是重定义或者叫重映射。比如如果你既要用USART2的TX引脚又要用TIM2的CH3通道它俩冲突成没办法同时用那我们就可以在这个重映射的列表里找一下比如这里我们找到了TIM2的CH3那TIM2的CH3就可以从原来的引脚,换到这里的引脚这样就避免了两个外设引脚的冲突。如果这个重映射的列表里找不到那外设复用的GPIO就不能挪位置这就是重映射的功能配置重映射是用AFIO来完成的重映射在最后会讲 作用根据TIM_OCInitStruct中指定的参数初始化TIMx channel。 参数说明 TIM_OCInitTypeDef structure结构体说明 实际上通用计时器只用到了这些结构体成员但结构体里面还有些成员是面向高级定时器比如 但是如果当你中途想把高级定时器当做通用定时器输出PWM时那你自然就会把TIM_OCXInit的TIM2改成TIM1。这样的话这个结构体原本没有用到的成员现在需要使用但是对于那些成员并没有赋值,那就会导致高级定时器输出PWM出现一些奇怪的问题最终找到的原因就是因为这里结构体成员没有配置完整。所以为了避免程序中出现不确定的因素把结构体所有的成员都配置完整要么就先给结构体成员都赋一个初始值再修改部分的结构体成员 所以void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct)有了用武之地。 作用TIM_OCInitStruct 中的每一个参数按缺省值填入 参数说明 第四步配置GPIO把PWM对应的GPIO口初始化为复用推挽输出的配置。为什么选择这个模式呢?对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的 那通过刚才看到引脚定义表我们就知道了这里片上外设引脚连接的就是TIM2的CH1通道。所以只有把GPIO设置成复用推挽输出引脚的控制权才能交给片上外设PWM波形才能通过引脚输出。 那最后第五步就是运行控制了启动计数器这样就能输出PWM了。 void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1)通道1 作用设置TIMx捕获比较寄存器值CCR 参数说明 重映射 根据你所要重映射的引脚在下图找到所需要的模式比如如果我们想把PA0改到PA15就可以选择这个部分重映射方式1或者完全重映射。 PA15在引脚定义图里没有加粗因为它上电后已经默认复用为了调试端口JTDI所以如果想让他作为普通的GPIO或者复用定时器的通道。那还需要先关闭调试端回的复用也是用这个GPIO PinRemapConfig函数 如果我们需要用PA15、PB3、PB4这三个引脚当做GPIO来使用的话那通常就是解除JTAG的复用保留SWD的复用那就加一下下面的第一句和第三句先打开AFIO时钟再用AFIO将JTAG复用解除掉这样就行了 如果你想重映射定时器或者其他外设的复用引脚那就加一下这里的第一句和第二句先打开AFIO时钟再用AFIO重映射外设复用的引脚这样就行了 如果你重映射的引脚又正好是调试端口那这三句就都得加上打开AFIO时钟重映射引脚解除调试端口这样才行。 main.c #include stm32f10x.h // Device header #include Delay.h #include OLED.h #include PWM_LED.huint8_t i;int main(void) {OLED_Init(); //初始化OLEDpwm_init();while(1){//不断调用PWM_SetCompare1函数更改CCR的值实现LED呼吸灯的效果for(i0;i100;i){PWM_SetCompare1(i);//设置CCR寄存器的值Delay_ms(10);}for(i0;i100;i){PWM_SetCompare1(100-i);Delay_ms(10);}} }PWM_LED.c #include stm32f10x.h // Device headervoid pwm_init(void) {//1.打开时钟选择内部时钟//使用APB1的开启时钟函数TIM2是APB1总线的外设RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开时钟//引脚重映射功能定时器通道1 PA0引脚重映射到PA15将下面初始化结构体GPIO_InitStructure引脚改为PA15其它不动 // RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//引脚重映射引脚重映射TIM2的CH1本来是挂载在PA0引脚的现在我想在其他引脚使用TIM2的CH1通道 // GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//参考手册AFIO。将PA0引脚重映射到PA15第一个参数可以是GPIO_PartialRemap1_TIM2或GPIO_FullRemap_TIM2 // GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//关闭调试端口复用JTAGPA15端口默认使用JTAG调试端口需要关闭SWJ就是SWD和JTAG两种调试方式若想用PA15\PB3\PB4三个引脚做GPIO使用先打开AFIO再将JTAG复用解除//2.初始化时基单元//选择时基单元的时钟,选择内部时钟;若不调用这个函数系统上电也是默认是内部时钟TIM_InternalClockConfig(TIM2);//3.配置时基单元 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; //指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; //计数器模式/*公式PWM频率Freq CK_PSC / (PSC 1) / (ARR 1)PWM占空比Duty CCR / (ARR 1)PWM分辨率Reso 1 / (ARR 1)若PWM波形为频率为1KHz占空比为50%分辨率为1%CK_PSC72MHz代入公式Freq 1000Hz72MHz / 720 / 100Duty 50% 50 / 100Reso 1% 1 / 100因此PSC719ARR99ARR50*/TIM_TimeBaseInitStructure.TIM_Period 100 - 1; //ARR 周期TIM_TimeBaseInitStructure.TIM_Prescaler 720 - 1; //PSC 预分频器TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器的值TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2,TIM_FLAG_Update);//4.初始化输出比较单元通道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 50;//设置CCR捕获比较寄存器值Pulse直译是脉冲TIM_OC1Init(TIM2, TIM_OCInitStructure);//在TIM2的OC1通道上就可以输出PWM波形了但最终要借用一下GPIO口才能输出查看引脚定义为PA0//5.初始化GPIOGPIO_InitTypeDef GPIO_InitStructure; //结构体变量名GPIO_InitStructureGPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //复用推挽输出PWM波形通过引脚输出使用定时器来控制引脚输出数据寄存器将被断开输出控制权将转移给片上外设这里片上外设引脚连接的就是TIM2的CH1通道GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; //默认50mhz输出GPIO_Init(GPIOA,GPIO_InitStructure); //使用的是地址传递 //6.启动定时器TIM_Cmd(TIM2,ENABLE);//PWM波形就能通过PA0输出了 }//让LED呈现呼吸灯的效果那就是不断更改CCR的值就行了 //在运行过程更改CCR使用函数TIM_SetCompare1封装用来单独更改通道1的CCR值//TIM_SetCompare1封装 void PWM_SetCompare1(uint16_t Compare1) {TIM_SetCompare1(TIM2,Compare1);}引脚重映射实验 PWM驱动舵机 这里一定要注意正负极接错可能会烧坏电脑 SG90舵机它有三根线第一个GND就是棕色线接在面包板的GND第二个5V正极就是红色线这里要接5V的电机电源大家不要把它接在面包板的正极了这个STM32芯片正极只有3.3V的电压而且输出功率不够带不动电机的所以我们需要把它接在STLINK的5V输出引脚然后看第三个引脚PWM信号就是橙色线接在PA1引脚上这里用的是PA1的通道2上一个代码用的PA0的通道1【看数据手册里面的引脚定义表PA0的复用功能是TIM2_CH1(通道一),PA1的复用功能是TIM2_CH2通道2】 那最后再在PB1接一个按键用来控制舵机这样这个电路就完成了。 驱动舵机的关键就是输出一个下面一样的PWM波形只要波形能够按照如下规定准确的输出那驱动舵机就非常简单了。 main.c #include stm32f10x.h // Device header #include Delay.h #include OLED.h #include serve.h #include key.huint8_t keynum; //按键键码 float angle;//角度变量int main(void) {OLED_Init(); //初始化OLEDserve_init();key_init();OLED_ShowString(1, 1 ,angle:);//serve_setangle(120); //舵机设置角度//PWM_SetCompare2(500); //对应舵机0度的位置2500对应180度1500对应90度//建立一个舵机模块封装函数。调用函数就能变为对应的角度舵机设置角度参数是0到180度while(1){keynum key_getnum();if(keynum 1){angle 30;if(angle 180){angle 0;}}serve_setangle(angle); //舵机设置角度 按键按一下旋转30度OLED_ShowNum(1,7,angle,3);//一行七列显示angle变量长度为3} }pwm_led.c #include stm32f10x.h // Device header/* pwm初始化函数基本步骤参考笔记PWM基本结构图 第一步RCC开启时钟把要用的TIM外设和GPIO外设的时钟打开 第二步配置单元包括时钟源选择和时基单元都配置好 第三步配置输出比较单元包括CCR值、输出比较模式、极性选择、输出使能这些参数在库函数里也是用结构体统一来配置 第四步配置GPIO把PWM对应的GPIO口初始化为复用推挽输出的配置Pwm和GPIO的对应关系可以参考引脚定义表 第五步运行控制启动计数器这样就能输出PWM了 *///驱动舵机用的是PA1口的通道2 void pwm_init(void) {//1.打开时钟选择内部时钟//使用APB1的开启时钟函数TIM2是APB1总线的外设RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开时钟//2.初始化时基单元//选择时基单元的时钟,选择内部时钟;若不调用这个函数系统上电也是默认是内部时钟TIM_InternalClockConfig(TIM2);//3.配置时基单元 /***********************************************************公式PWM频率 Freq CK_PSC / (PSC 1) / (ARR 1)PWM占空比Duty CCR / (ARR 1)PWM分辨率Reso 1 / (ARR 1)************************************************************若PWM波形为频率为1KHz占空比为50%分辨率为1%舵机要求的周期是20ms频率就是1/20ms50hz舵机要求高电平时间是0.5ms-2.5ms也就是占空比ARR设置为20k对应20ms计数器加一次就是1usCCR设置500就是0.5ms设置2500就是2.5ms*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; //指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; //计数器模式TIM_TimeBaseInitStructure.TIM_Period 20000 - 1; //ARR 周期TIM_TimeBaseInitStructure.TIM_Prescaler 72 - 1; //PSC 预分频器TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器的值TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2,TIM_FLAG_Update);//4.初始化输出比较单元通道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 50;//设置CCRPulse直译是脉冲TIM_OC2Init(TIM2, TIM_OCInitStructure);//OC2是通道2通道和引脚是对应的对于同一个定时器的不同通道输出的PWM的特点如后因为不同通道共用一个计数器所以它们的频率必须是一样的它们的占空比由各自的CCR决定的由于计数器的更新所有PWM同时跳变所以它们的相位是同步的这就是同一个定时器不同通道输出PWM的特点如果驱动多个舵机或者直流电机那使用一个定时器不同通道的pwm就完全可以了/*上面这个结构体的参数就会配置到通道2的当然如果你通道1和通道2都想要用的话那就直接在这里加两行代码把通道1和通道2都初始化了这样就能同时使用两个通道来输出两个PWM了同理通道3和通道4也是可以使用的*///5.初始化GPIOGPIO_InitTypeDef GPIO_InitStructure; //结构体变量名GPIO_InitStructureGPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //复用推挽输出PWM波形通过引脚输出使用定时器来控制引脚输出数据寄存器将被断开输出控制权将转移给片上外设这里片上外设引脚连接的就是TIM2的CH1通道GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; //默认50mhz输出GPIO_Init(GPIOA,GPIO_InitStructure); //使用的是地址传递 //6.启动定时器TIM_Cmd(TIM2,ENABLE);//PWM波形就能通过PA0输出了 }//TIM_SetCompare2封装使用通道2 void PWM_SetCompare2(uint16_t Compare) {TIM_SetCompare2(TIM2,Compare);}serve.c #include stm32f10x.h // Device header #include PWM_LED.h //继承pwm的功能//舵机初始化函数void serve_init(void) {pwm_init();//将pwm底层初始化 }/* 0度 对应 CCR 500 180 2500 对angle进行缩放。0-180是180范围500-2500是2000范围所以angle / 180*2000 500偏移就得到目标比例了完成0-180到500-2500的映射了 */void serve_setangle(float angle) //舵机设置角度 {PWM_SetCompare2(angle / 180 * 2000 500);//线性映射 }PWM驱动直流电机 就是小时候玩的赛车里的那种电机 这里红色的是TB6612电机驱动模块第一个引脚VM是电机电源接在STLINK的5v引脚第二个VCC逻辑电源接在面包板3.3v正极AO1和AO2是电机输出端接电机的两根线接线不分正反对调两根线电机的旋转方向就会反过来然后右边这里是另一路的驱动如果你需要接两个电机那就在右边再接一个电机如果只需要一个电机那就随便选一路另一路空着就行STBY是待机控制脚不需要待机直接接逻辑电源正3.3v控制引脚 AIN1和AIN2是方向控制任意接两个GPIO就可以这里接PB4和PB5两个脚控制引脚 PWMA是速度控制需接PWM的输出脚接PA2引脚PA2对应的是TIM2的通道3(我们又换了一个通道)这里还接了一个按键接PB1口用于控制 电机旋转会发出蜂鸣器的声音加大PWM频率当PWM频率足够大时超出人耳的范围人耳就听不到了人耳听到的范围是20Hz到20KHz。可以减小预分频器PSC来加大频率且不会影响占空比。 main.c #include stm32f10x.h // Device header #include Delay.h #include OLED.h #include moter.h #include key.huint8_t keynum;//按键键码 int8_t speed;//有符号的速度变量int main(void) {OLED_Init(); //初始化OLEDmoter_init();key_init();OLED_ShowString(1,1,speed:);while(1){ keynum key_getnum();if(keynum 1){speed 20;if(speed 100){speed -100;//speed从-100到100变化} }moter_setspeed(speed);//实现按键控制速度OLED_ShowSignedNum(1,7,speed,3);} }PWM_LED.c #include stm32f10x.h // Device header/* pwm初始化函数基本步骤参考笔记PWM基本结构图 第一步RCC开启时钟把要用的TIM外设和GPIO外设的时钟打开 第二步配置单元包括时钟源选择和时基单元都配置好 第三步配置输出比较单元包括CCR值、输出比较模式、极性选择、输出使能这些参数在库函数里也是用结构体统一来配置 第四步配置GPIO把PWM对应的GPIO口初始化为复用推挽输出的配置Pwm和GPIO的对应关系可以参考引脚定义表 第五步运行控制启动计数器这样就能输出PWM了 *///1.电机接在TIM2的通道3上。修改GPIO_Pin_2。TIM_OC3Init。PWM_SetCompare3 //2.对于直流电机也建立一个hardware模块void pwm_init(void) {//1.打开时钟选择内部时钟//使用APB1的开启时钟函数TIM2是APB1总线的外设RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开时钟//2.初始化时基单元//选择时基单元的时钟,选择内部时钟;若不调用这个函数系统上电也是默认是内部时钟TIM_InternalClockConfig(TIM2);//3.配置时基单元 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; //指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; //计数器模式/*公式PWM频率Freq CK_PSC / (PSC 1) / (ARR 1)PWM占空比Duty CCR / (ARR 1)PWM分辨率Reso 1 / (ARR 1)若PWM波形为频率为1KHz占空比为50%分辨率为1%CK_PSC72MHz代入公式Freq 1000Hz72MHz / 720 / 100Duty 50% 50 / 100Reso 1% 1 / 100因此PSC719ARR99ARR50*/TIM_TimeBaseInitStructure.TIM_Period 100 - 1; //ARR 周期TIM_TimeBaseInitStructure.TIM_Prescaler 36 - 1; //PSC 预分频器,现在为20KHz人耳听不到了TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器的值TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2,TIM_FLAG_Update);//4.初始化输出比较单元通道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 50;//设置CCRPulse直译是脉冲TIM_OC3Init(TIM2, TIM_OCInitStructure);//TIM2通道3//5.初始化GPIOGPIO_InitTypeDef GPIO_InitStructure; //结构体变量名GPIO_InitStructureGPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //复用推挽输出PWM波形通过引脚输出使用定时器来控制引脚输出数据寄存器将被断开输出控制权将转移给片上外设这里片上外设引脚连接的就是TIM2的CH1通道GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; //默认50mhz输出GPIO_Init(GPIOA,GPIO_InitStructure); //使用的是地址传递 //6.启动定时器TIM_Cmd(TIM2,ENABLE);//PWM波形就能通过PA0输出了 }//TIM_SetCompare1封装 void PWM_SetCompare3(uint16_t Compare) {TIM_SetCompare3(TIM2,Compare);}moter.c #include stm32f10x.h // Device header #include PWM_LED.h //继承PWM模块void moter_init(void) //初始化函数 {pwm_init();//调用底层的PWM_init初始化pwm//需要额外初始化方向控制的两个脚即初始化GPIO引脚RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开时钟//配置端口模式GPIO_InitTypeDef GPIO_InitStructA; //结构体变量名GPIO_InitStructAGPIO_InitStructA.GPIO_Mode GPIO_Mode_Out_PP; //推挽输出GPIO_InitStructA.GPIO_Pin GPIO_Pin_4 |GPIO_Pin_5; //或运算选择两个引脚GPIO_InitStructA.GPIO_Speed GPIO_Speed_50MHz; //默认50mhz输出GPIO_Init(GPIOA,GPIO_InitStructA); //使用的是地址传递 }//设置速度的函数 -100 ~ 100 void moter_setspeed(int8_t speed) {//针对正转和翻转用if来分别处理if(speed 0)//正转的逻辑{//首先将方向控制脚设置为一个高电平一个低电平.哪个为高哪个为底无所谓GPIO_SetBits(GPIOA,GPIO_Pin_4);GPIO_ResetBits(GPIOA,GPIO_Pin_5);//速度PWM_SetCompare3(speed);}else//speed就是负数代表反转{//首先是正反转将set和reset反过来就能反转了 GPIO_ResetBits(GPIOA,GPIO_Pin_4);GPIO_SetBits(GPIOA,GPIO_Pin_5);PWM_SetCompare3(-speed);//此时speed为负数必须为正数在speed前加负号}}有很多地方可以改变极性比如最简单的电机两根线反过来接或者输入的AIN1和AIN2反过来在程序里也可以把这里的GPIO_ResetBits和GPIO_SetBits反过来这都可以改变极性。 TIM输入捕获 输入捕获简介 输入捕获对于PID控制算法很重要没有输入捕获就不能完成闭环控制要做平衡车的一定要认真学 输入捕获就是这左边一部分电路那右边就是我们之前讲过的输出比较部分四个输入捕获和输出比较通道共用四个CCR寄存器另外它们的CH1到CH44个通道的引脚也是共用的所以对于同一个定时器输入捕获和输出比较只能使用其中一个不能同时使用。 输入捕获即Input Capture英文缩写为IC。输入捕获模式下当通道输入引脚出现指定电平跳变的瞬间程序里可以定义为上升沿、下降沿当前CNT的值将被锁存到CCR中就是把当前CNT的值读出来写入到CCR中去检测电平跳变然后执行动作左边这四个是边沿信号输入引脚一旦有边沿比如说上升沿那这一块输入滤波和边缘检测电路就会检测到这个上升沿让输入捕获电路产生动作作用和外部中断差不多只不过外部中断执行的动作是向CPU申请中断输入捕获执行的是控制后续电路让当前CNT的值锁存到CCR寄存器中可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数。在这里脉冲间隔和频率差不多、电平持续时间和占空比也是互相对应的关系。 每个高级定时器和通用定时器都拥有4个输入捕获通道且二者没有区别。基本定时器没有输入捕获的功能。 输入捕获模块可以配置为PWMIPWM输入模式模式和主从触发模式。PWMI模式是PWM的输入模式专门用来同时测量PWM波形的频率和占空比的。主从触发模式可以实现对频率或占空比的硬件的全自动测量。PWMI模式和主从触发模式设计得非常巧妙把这两个功能结合起来测量频率和占空比就是硬件全自动执行软件不需进行任何干预也不需进中断需要测量的时候直接读取CCR寄存器就行了使用非常方便且极大地减轻了软件的压力。 如下图左边为输入捕获电路4个输入捕获和输出比较通道共用4个CCR寄存器另外它们的CH1到4的4个通道引脚也是共用的所以对于同一个定时器输入捕获和输出比较只能使用其中一个不能同时使用。 输入捕获对比输出比较 输出比较引脚是输出端口根据CNT和CCR的大小关系来执行输出动作输入捕获引脚是输入端口接收到输入信号执行CNT锁存到CCR的动作 频率的测量方法 如上图是频率逐渐降低的方波波形越往左频率越高越往右频率越低这里信号都是只有高低电平的数字信号对于STM32测频率而言它也是只能测量数字信号的。如果需要测量一个正弦波则需要搭建一个信号预处理电路最简单的就是用运放搭建一个比较器把正弦波转换为数字信号再输入给STM32就行了如果你测量的信号电压非常高那还要考虑隔离的问题比如使用隔离放大器、电压互感器等元件隔离高压端和低压端保证电路的安全。总之经过处理最终输入给STM32的信号是如上图的高低电平信号高电平3.3v低电平0v。 为了测量频率有两种方法可以选择测频法、测周法 测频法定时器中断并记录捕获次数测周法捕获中断并记录定时器次数。 测频法 测频法的测试方法直接按频率定义来进行测量的方法是在闸门时间T内对上升沿也可以是下降沿计次得到N则待测信号频率 为 / 例如可以定义闸门时间T1s 砸门时间不是必须为1s则在一秒中得到的上升沿的个数每来一个上升沿就是完整的一个周期的信号个数就是频率。 频率的定义就是1s内出现了多少个重复的周期那频率就是多少Hz。 测周法 测周法的测试方法是两个上升沿内以标准频率fc计次fc72M/psc1得到N(就是读取CCR的值则测量频率为 / 测周法的基本思想是周期的倒数就是频率。如果我们能用定时器测量出一个周期的时间相邻上升沿或相邻下降沿的间隔时间取倒数即得到测量频率。 捕获信号的两个上升沿然后测量一下两个上升沿之间持续的时间但是实际上并没有一个精度无穷大的秒表来测量时间测量时间的方法实际上也是定时器计次我们使用一个已知的标准频率fc的计次时钟来驱动计数器从一个上升沿开始计数器从0开始一直计到下一个上升沿停止计一个数的时间是1/fc计N个数时间就是N/fc也就是周期再取倒数就得到了频率fx 那这两种方法都有什么区别呢 首先测频法适合测量高频信号测周法适合测量低频信号这个从图里也可以看出来测频法在闸门时间内最好要多出现一些上升沿计次数量多一些这样有助于减小误差哈假如你定了一秒的闸门时间结果信号频率非常低一秒的时间才只有寥寥无几的几个上升沿甚至一个上升沿都没有那你总不能认为频率是零吧在计次N很少时误差会非常大所以测频法要求信号频率要稍微高一些 。那对于侧周法就要求信号频率低一些了低频信号周期比较长计次就会比较多有助于减小误差 否则的话比如标准频率fc为一兆赫兹待测信号频率太高比如待测信号500 千赫兹那在一个周期内只能记一两个数啊如果待测信号再高一些甚至一个数也记不到那总不能认为频率无穷大吧 所以测周法需要待测信号频率低一些然后是测频法测量结果更新的慢一些数值相对稳定测周法更新的快数据跳变也非常快。 从原理上看测频法自带一个均值滤波的功能如果在闸门时间 T内被测频率有变化测频法得到的实际是这一段闸门时间内的平均频率如果咱们时间选为一秒那么每隔一秒才能得到一次结果所以测频法结果更新慢测量结果是一段时间的平均值值比较平滑反观测周法它只测量一个周期就能出一次结果所以出结果的速度取决于待测信号的频率一般而言待测信号都是几百几千赫所以一般情况下测周法结果更新更快测周法只测量一个周期故其结果会受噪声的影响波动会比较大。 对于测频法和测周法的一个共同点是N越大误差就越小。在两种方法中计次都可能会产生正负1误差。在测频法的一个闸门时间内并不是每一个被测信号的周期都是完整的比如在最后时间里 可能有一个周期刚出现一半咱们时间就到了那这只有半个周期只能舍弃掉,或者当作一整个周期来看因为g是只有整数不可能计次0.5个数吧那在这个过程就会出现多记一个或者少记一个的情况这就叫做正负1误差测周法的标准计数信号的信号也不一定是被测信号的整数倍所以它也不一定是每一个都完整的。对于上述的两种情况都会出现多计一个数或者少计一个数的情况所以会产生正负1误差。要想减小正负1误差的影响就只能尽量多记一些数当计值n比较大时正负1对n的影响就会很小。 刚才我们说了高频适合使用测评法低频适合使用测周法那多高算高频多低算低频我们有以下一个参数来考量中界频率测频法和测周法误差相等的频率点。由于两种方法的误差都与N的正负1误差有关所以当两种方法计次的N相同时两种方法的误差也就相同。消去两种方法公式中的N将测频法和测周法的N提出来令两个方法N相等将fx解出来可得 式中T 是测频法的闸门时间f c是测频法的标准频率。 当待测信号频率小于中界频率时测周法误差更小选择测周法更合适当待测信号频率大于中界频率时测频法误差更小选择测频法更合适。 2.4用STM32来实现测频法和测周法 测频法之前学过的外设可以实现对射式红外传感器计次、定时器外部时钟这些代码稍加改进就是测频法比如对射式红外传感器计次每来一个上升沿计次1再用一个定时器定一个1s的定时中断在中断里每隔1s取一下计次值同时清0为下一次做准备这样每次读取的计次值就直接是频率对应定时器外部时钟的代码也是如此每隔1s取一下计次就能实现测频法测量频率的功能了。 本节输入捕获测频率使用的方法是测周法。就是测量两个上升沿之间的时间来进行频率测量。 输入捕获的各部分电路 从左向右依次进行电路分析 最左边是四个通道的引脚参考引脚定义表就能知道引脚是复用在哪个位置然后引脚进来有一个三输入的异或门这个异或门的输入接在了通道1、2、3端口异或门的执行逻辑是当三个输入引脚的任何一个有电平翻转时输出引脚就产生一次电平翻转3个引脚电平都相同为03个引脚中有高有低为1。 然后输出通过数据选择器到达输入捕获通道1数据选择器如果选择上面一个那输入捕获通道1的输入就是三个引脚的异或值若选择下面一个异或门就没有用到四个通道各用各的引脚。设计异或门其实还是为了三相无刷电机服务的无刷电机有三个霍尔传感器检测转子的位置可以根据转子的位置来进行换相。有了这个异或门就可以在前三个通道接上无刷电机的霍尔传感器然后这个定时器就作为无刷电机的接口定时器去驱动换相电路工作这个了解一下我们本节暂时不涉及。 输入信号来到了输入滤波器和边沿检测器极性选择。输入滤波器可以对信号进行滤波避免一些高频的毛刺信号误触发边沿检测器就是和外部中断一样可以选择高电平触发或者低电平触发当出现指定的电平时边沿检测电路就会触发后续电路执行动作。这里设计了两套滤波和边沿检测电路第一套电路经过滤波和极性选择得到TI1FP1输入给通道1的后续电路。第二套电路经过另一个滤波和极性选择得到TI1FP2输入给通道2的后续电路同理下面TI2信号进来也经过两套滤波和极性选择得到TI2FP1输入通道1和TI2FP2输入通道2其中TI2FP1输给上面TI2FP2输入给下面在这里两个信号进来可以选择各走各的也可以选择进行一个交叉。可以进行交叉连接例如CH1引脚输入给通道2CH2引脚输入给通道1进行交叉连接的目的是两个1.第一个目的可以灵活切换后续捕获电路的输入比如你一会儿想以CH1作为输入一会儿想以CH2作为输入这样就可以通过这个数据选择器灵活的进行选择。2.第二个目的也是它交叉的主要目的就是可以把一个引脚的输入同时映射到两个捕获单元这也是PWMI模式的经典结构等会讲PWMI模式的时候你就知道这样设计的好处了第一个捕获通道使用上升沿触发用来捕获周期第二个通道使用下降沿触发用来捕获占空比两个通道同时对一个硬件进行捕获就可以同时测量频率和占空比这就是PWMI模式等会儿再来继续分析啊那先回到这里一个通道灵活切换两个引脚和两个通道同时捕获一个引脚这就是这里交叉一下的作用和目的同样下面通道三和通道四也是一样的结构另外这里还有一个TRC信号也可以选择作为捕获部分的输入这个TRC信号是来源于这里的(上方)这样设计也是为了无刷电机的驱动哈这个知道一下就行了我们暂时不用。 输入信号进行滤波和极性选择后就来到了预分频器每个通道各有一个预分频器可以选择对前面的信号进行分频分频之后的触发信号就可以触发捕获电路进行工作了每来一个触发信号CNT的值就会向CCR转运一次转运的同时会发生一个捕获事件这个事件会在状态寄存器置标志位同时也可以产生中断如果需要在捕获的瞬间处理一些事情的话就可以开启这个捕获中断比如可以配置上升沿触发捕获每来一个上升沿CNT转运到CCR一次又因为CNT计数器是由内部的标准时钟驱动的所以CNT的数值可以用来记录两个上升沿之间的时间间隔周期再取倒数就是测周法测量的频率了在每次捕获后将CNT清零可以用主从触发模式来自动完成CNT清零这样下次上升沿再捕获的时候取出的CNT才是两个上升沿的时间间隔。 到这里输入捕获电路的执行流程和测频率的原理我们已经大概了解了接下来就是执行细节的问题 把电路执行的细节都了解清楚这样写程序的时候才能得心应手。 输入捕获通道1的详细框图如上所示是前面框图的一个细化结构基本功能都是一样的 引脚进来先经过一个滤波器滤波器的输入是TI1就是CH1的引脚输出的TI1F就是滤波后的信号 FDTS是滤波器的采样时钟来源 CCMR1寄存器里的ICF位可以控制滤波器的参数 滤波器的工作原理就是以采样频率对输入信号进行采样当连续N个值都为高电平输出才为高电平当连续N个值都为低电平输出才为低电平如果信号出现高频抖动导致连续采样N个值不全都一样那输出就不会变化这样就可以达到滤波的效果。采样频率越低采样个数N越大滤波效果就越好。上面表格里的描述就是每个参数对应的采样频率和采样个数在实际应用中如果波形噪声比较大就可以把IC1F位参数设置大一点来过滤噪声。 滤波之后的信号通过边沿检测器捕获上升沿或者下降沿CCER寄存器里的CC1P位可以进行极性选择最终得到TI1FP1触发信号通过数据选择器进入通道1后续的捕获电路。 当然还有一套一样的电路得到TI1FP2触发信号连通到通道2的后续电路上图并没有画出来同样通道2有TI2FP1连通到通道1的后续通道2也有TI2FP2连通到通道2的后续总共有四种连接方式然后经过数据选择器进入后续捕获部分电路。CCMR寄存器的CC1S位可以对数据选择器进行选择之后CCMR寄存器的ICPS位可以配置分频器可以选择不分频、2分频、4分频、8分频 CCMR寄存器的CC1E位控制输出使能或失能。如果使能了输出输入端产生指定边沿信号经过层层电路就可以最后将CNT的值转运到CCR里来每捕获一次CNT的值都要把CNT清0一下以便于下一次的捕获从模式控制器就可以在捕获之后自动完成CNT的清零工作。 在这里硬件电路就可以在捕获之后自动完成CNT的清0工作如何自动清0 CNT呢看一下这里这个TI1FP1信号和TI1的边缘信号都可以通向从模式控制器比如TI1FP1信号的上升沿触发捕获那通过这里TI1FP1还可以同时触发从模式这个从模式里面就有电路可以自动完成CNT的清零所以可以看出这个从模式就是完成自动化操作的利器。 输入捕获的主模式、从模式、触发源选择简称主从触发模式 主从触发模式有什么用如何来完成硬件自动化的操作我们看一下主从触发模式这个名字是江科大老师起的手册里并没有这个描述。 主从触发模式即主模式、从模式和触发源选择三个功能的简称。主模式可以将定时器内部的信号映射到TRGO引脚用于触发其他外设的操作从模式可以接收其他外设或自身外设的一些信号用于触发自己的一些操作定时器的运行触发源选择即选择从模式的触发信号源功能也可以认为它是从模式的一部分。 在从模式下可以通过触发源选择功能选择一个信号产生TRGI信号之后去触发从模式从模式可以在上面列表中选择一项操作来自动执行。如果想完成我们刚才说的任务想让TI1FP1信号自动触发CNT清零 那触发源选择就可以选中这里的TI1FP1从模式执行的操作就可以选择执行reset的操作 这样TI1FP1的信号就可以自动触发从模式从模式自动清零CNT实现硬件全自动测量。 关于主从模式的详细说明可以参见手册 比如你想实现定时器的级联就可以选择一个定时器主模式输出更新信号到TRGO另一个定时器选择上一个定时器触发从模式从模式选择执行外部时钟模式一的操作这样就能实现定时器的级联了 还有其他很多高级的功能都可以用主从触发模式来实现使用非常灵活。 然后主模式还可以选择复位、使能、比较脉冲和四个OCREF信号作为TIGO的输出具体描述可以看看这里的文字这些东西不用特别去记知道有这个功能就行需要用的时候再来看看。 然后主模式还可以选择复位、使能、比较脉冲和四个OCREF信号作为TIGO的输出具体描述可以看看这里的文字这些东西不用特别去记知道有这个功能就行需要用的时候再来看看。 继续往下这里有从模式触发源的可选信号可以选择这些信号去触发从模式触发从模式后可以执行哪些操作呢 继续看从模式选择可以执行这么多的操作比如我们本节会使用到复位模式执行的操作就是选中触发输入的上升沿重新初始化计数器是不是就是清零CNT的意思啊这就是从模式。 总结下来就是PPT这三个图主模式、触发源选择、从模式在库函数里也非常简单哈这三块东西就对应三个函数调用函数给个参数就行了这些就是主从触发模式的内容。 最后理一下思路把之前的东西组合在一起得到下面这两个图这两个图也分别对应了我们演示两个代码的逻辑。 先看第一个图输入捕获基本结构这个结构我们只使用了一个通道所以它目前只能测量频率在右上角这里是时基单元我们把时基单元配置好启动定时器那这个CNT就会在预分频之后的这个时钟驱动下不断自增这个CNT就是我们测周法用来计数计时的东西经过预分频之后这个位置的时钟频率就是驱动CNT的标准频率fc。 这里不难看出来标准频率等于72MHz/预分频系数然后下面输入捕获通道一的GPIO口输入一个图示绿色的方波信号经过滤波器和边沿检测选择TI1FP1为上升沿触发之后输入选择直连的通道 分频器选择不分屏当TI1FP1出现上升沿之后CNT的当前记录值转运到CCR1里同时触发源选择选中TI1FP1为触发信号从模式选择复位操作这样TI1FP1的上升沿也会通过上面这一路去触发CNT清零当然这里会有个先后顺序哈肯定是得先转运CNT的值到CCR里去再触发从模式给CNT清零。或者是非阻塞的同时转移CNT的值转移到CCR同时0转移到CNT里面去总之是先捕获再清零。 当电路不断工作时CCR1中的值始终是最新一个周期的计数值即测周法的计次数 N。所以当我们想读取信号的频率时只需要读取CCR1得到N再计算fc/N就得到频率了。当不需要读取时整个电路全自动的测量不需要占用任何软件资源。 这里需要注意以下两点 CNT的计数值是有上限的。由于ARR最大为65535故CNT最大也只能计65535个数。如果信号频率太低CNT的计数值可能会溢出。从模式的触发源选择中有TI1FP1和TI2FP2但是没有TI3和TI4的信号。所以如果要使用从模式自动清零CNT就必须使用通道1或通道2作为输入。对于通道3和通道4就只能开启捕获中断在中断中手动清0了这样程序会处于频繁中断的状态比较占用软件资源。 第二个图PWMI基本结构 PWMI模式使用两个通道同时捕获一个引脚可以同时测量周期和占空比相比前面输入捕获下面多了一个TI1FP2的通道。 首先TI1FP1配置上升沿触发触发捕获和清零CNT正常的捕获周期再来一个TI1FP2配置为下降沿触发通过交叉通道去触发通道2的捕获单元最开始上升沿CCR1捕获同时清零CNT之后CNT一直加加然后在下降沿时刻触发CCR2捕获这时CCR2的值就是CNT从上升沿到下降沿的计数值也就是高电平期间的计数值CCR2捕获并不触发CNT清零所以CNT继续加加直到下一次上升沿CCR1捕获周期并CNT清零这样执行之后CCR1就是一整个周期的计数值CCR2就是高电平期间的计数值用CCR2/CCR1就是占空比以上就是PWMI模式使用两个通道来捕获频率和占空比的思路。另外也可以两个通道同时捕获第一个引脚的输入这样通道二的前面这一部分就没有用到行当然也可以配置两个通道同时捕获第二个引脚的输入这样我们就是使用TI2FP1和TI2FP2这两个引脚了这两个输入可以灵活切换。 手册 手册本节相应的内容描述是寄存器的方式不太好理解需要结合上面笔记内容进行进一步理解就比较容易理解了。 上图就是PWMI模式的例子一个通道捕获周期也就是频率一个通道捕获脉宽也就是占空比这就是这两种输入捕获的模式另外我们本节还讲了主从触发模式这个有什么用 在定时器同步这里有介绍包括使用一个定时器作为另一个定时器的预分频器就是定时器级联。 这些定时器同步的功能比较高级了解一下就行了主要还是理解这个从模式在这里配合输入捕获完成硬件自动化的用途。 中间这部分是另一个博主笔记。 输入捕获电路的工作流程 由四个问题来深入输入捕获的工作流程 输入捕获和输出比较的区别 为什么要进行一个交叉连接呢 滤波器具体是怎么工作的呢 如何自动清零CNT呢 输入捕获和输出比较的区别 对比一下输出比较就是 输出比较引脚是输出端口输入捕获引脚是输入端口 输出比较是根据CNT和CCR的大小关系来执行输出动作输入捕获是接收到输入信号执行CNT锁存到CCR的动作。 交叉连接的目的 为什么要进行一个交叉连接呢 这样做的目的个人认为主要有两个第一个目的可以灵活切换后续捕获电路的输入第二个目的也是它交叉的主要目的就是可以把一个引脚的输入同时映射到两个捕获单元这也是PWMI模式的经典结构。第一个捕获通道使用上升沿触发用来捕获周期第二个通道使用下降沿触发用来捕获占空比。两个通道同时对一个引脚进行捕获就可以同时测量频率和占空比这就是PWMI模式等会儿再来继续分析。一个通道灵活切换两个引脚和两个通道同时捕获一个引脚这就是这里交叉一下的作用和目的。同样下面通道3和通道4也是一样的结构可以选择各自独立连接也可以选择进行交叉。另外这里还有一个TRC信号也可以选择作为捕获部分的输入这样设计也是为了无刷电机的驱动。 到这里电路的整个工作流程讲完了。比如我们可以配置上升沿触发捕获每来一个上升沿CNT转运到CCR一次又因为这个CNT计数器是由内部的标准时钟驱动的所以CNT的数值其实就可以用来记录两个上升沿之间的时间间隔这个时间间隔就是周期再取个倒数就是测周法测量的频率了。另外这里还有个细节问题就是每次捕获之后我们都要把CNT清0一下这样下次上升沿再捕获的时候取出的CNT才是两个上升沿的时间间隔这个在一次捕获后自动将CNT清零的步骤我们可以用主从触发模式自动来完成。 接下来就是执行细节的问题把电路执行的细节都了解清楚这样写程序的时候才能得心应手。好那接着看一下这里这是输入捕获通道1的一个更详细的框图基本功能都是一样的。 滤波器具体是怎么工作的呢 可以看一下手册在CCMR1寄存器这里有IC1F位根据它的描述简单理解这个滤波器工作原理就是以采样频率对输入信号进行采样当连续N个值都为高电平输出才为高电平连续N个值都为低电平输出才为低电平。如果你信号出现高频抖动导致连续采样N个值不全都一样那输出就不会变化这样就可以达到滤波的效果。采样频率越低采样个数N越大说滤波效果就越好那下面这些描述就是每个参数对应的采样频率和采样个数。在实际应用中如果波形噪声比较大入100就可以把这个参数设置大一些这样就可以过滤噪声了。 如何自动清零CNT呢 看一下这里这个TI1FP1信号和TI1的边沿信号都可以通向从模式控制器比如TI1FP1信号的上升沿触发捕获,那通过这里TI1FP1还可以同时触发从模式这个从模式里面就有电路可以自动完成CNT的清零。所以可以看出这个从模式就是完成自动化操作的利器。 那接下来我们就来研究一下这个主从触发模式。主从触发模式有什么用如何来完成硬件自动化的操作。 主从触发模式就是主模式、从模式。 如果想完成我们刚才说的任务想让TI1FP1信号自动触发CNT清零那触发源选择就可以选中这里的TI1FP1从模式执行的操作就可以选择执行Reset的操作。这样TI1FP1的信号就可以自动触发从模式从模式自动清零CNT实现硬件全自动测量这就是主从触发模式的用途。 那有关这些信号的具体解释可以看手册 那回到PPT总结下来就是这三个图主模式触发源选择从模式在库函数里也非常简单。这三块东西就对应三个函数调用函数给个参数就行了这些就是主从触发模式的内容。接下来我们就来最后理一下思路把之前的东西组合在一起得到这两个图。这两个图也分别对应了我们演示两个代码的逻辑先看第一个输入捕获基本结构 然后还有几个注意事项说明一下首先是这里CNT的值是有上限的ARR—般设置为最大65535那CNT最大也只能计65535个数。如果信号频率太低CNT计数值可能会溢出因为CNT计数的快慢是根据时基单元的时钟频率而变化的如果时钟频率很高CNT增长非常快如果被测信号频率太低完全有可能CNT计满65536都不到被测信号的一个周期。另外还有就是这个从模式的触发源选择在这里看到只有TI1FP1和TI2FP2没有TI3和TI4的信号所以这里如果想使用从模式自动清零CNT就只能用通道1和通道2。对于通道3和通道4就只能开启捕获中断在中断里手动清零了不过这样程序就会处于频繁中断的状态比较消耗软件资源这个注意一下。 好接下来我们继续来看最后一个PPT这里展示的是PWMI基本结构。 这个PWMI模式使用了两个通道同时捕获一个引脚可以同时测量周期和占空比。 我们来看一下上面这部分结构和刚才演示的一样下面这里多了一个通道。 首先TI1FP1配置上升沿触发触发捕获和清零CNT正常地捕获周期这时我们再来一个TI1FP2配置为下降沿触发通过交叉通道去触发通道2的捕获单元这时会发生什么呢 我们看一下左上角的这个图最开始上升沿CCR1捕获同时清零CNT,之后CNT一直然后在下降沿这个时刻触发CCR2捕获所以这时CCR2的值就是CNT从这里到这里的计数值就是高电平期间的计数值CCR2捕获并不触发CNT清零所以CNT继续。 直到下一次上升沿CCR1捕获周期CNT清零这样执行之后CCR1就是一整个周期的计数值CCR2就是高电平期间的计数值我们用CCR2/CCR1是不是就是占空比了。这就是PWMI模式使用两个通道来捕获频率和占空比的思路。 另外这里你可以两个通道同时捕获第一个引脚的输入这样通道2的前面这一部分就没有用到。 当然也可以配置两个通道同时捕获第二个引脚的输入这样我们就是使用TI2FP1和TI2FP2这两个引脚了这两个输入可以灵活切换。 好到这里我们本小节的内容差不多就结束了最后大致看一下手册37:28 示例程序输入捕获模式测频率PWMI模式测频率和占空比 目前我们这个测量信号的输入引脚是PA6信号信号从PA6进来待测的PWM信号也是STM32自己生成的输出引脚是PA0所以接线这里直接用一根线把PA0引到PA6就行了如果你有信号发生器的话也可以设置成方波信号输出高电平3.3V低电平0V然后直接接到PA6。 目前我们要借用一下之前写好的pwm模块以便生成待测信号所以程序这里直接复制6-3 pwm驱动led呼吸灯的代码在这个工程的基础上写。 前置操作 PWM模块这里我们还要再进行一些改进。目前这个代码的逻辑是初始化TIM2的通道1产生一个PWM波形输出引脚是PA0。然后通过SetCompare1函数可以调节CCR1寄存器的值从而控制PWM的占空比。但是目前PWM的频率是在初始化里写好了的是固定的运行的时候调节不太方便所以我们在最后再加一个函数用来便捷地调节PWM频率。 如何调节PWM频率呢 通过公式我们知道PWM频率更新频率72M/(PSC1/(ARR1)所以PSC和ARR都可以调节频率但是占空比CCR/(ARR1)所以通过ARR调节频率还同时会影响到占空比而通过PSC调节频率不会影响占空比显然比较方便。所以我们的计划是固定ARR为100-1通过调节PSC来改变PWM频率另外ARR为100-1CCR的数值直接就是占空比用起来比较直观。 当然实际使用也是有技巧的一般我们可以根据分辨率的要求先确定好ARR比如分辨率1%就足够了那ARR给100-1这样PSC决定频率CCR决定占空比。如果我想要更高的分辨率比如0.1%那ARR就先固定1000-1这样频率就是72M/预分频/1000占空比就是CCR/1000这样也好算。 在这里目前ARR我们固定给100-1初始化操作的PSC就先不管我们后面再写一个函数在初始化之后单独修改PSC。 例如定义一个void PWM_SetPrescaler(uint16_t Prescaler)函数,在自定义函数里面我们就要调用库函数里单独写入PSC的函数了TIM_PrescalerConfig就是单独写入PSC的函数。因为这个函数还有一个重装模式的参数所以它并不叫SetPrescaler而叫PrescalerConfig。这是这个库的命名规范。 void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode) 可能是因为手册版本太低了并没有提到中间参数那我们就看库里面的注释 参数Prescaler要写入PSC的值。 接下来就可以写输入捕获的代码 所以步骤就是 第一步RCC开启时钟把GPIO和TIM的时钟打开 第二步GPIO初始化把GPIO配置成输入模式一般选择上拉输入或浮空输入模式 第三步配置时基单元让CNT计数器在内部时钟的驱动下自增运行和之前代码一样 第四步配置输入捕获单元包括滤波器、极性、直连通道还是交叉通道、分频器这些参数用一个结构体就可以统一进行配置了 第五步选择从模式的触发源触发源选择为TI1FP1这里调用一个库函数给一个参数就行了 第六步选择触发之后执行的操作执行Reset操作这里调用一个库函数就行了 最后当这些电路都配置好之后调用TIM_Cmd函数开启定时器。这样所有的电路就能配合起来了按照我们的要求工作了。当我们需要读取最新一个周期的频率时直接读取CCR寄存器然后按照fc/N计算一下就行了这就是整个程序的思路。 第一步RCC开启时钟把GPIO和TIM的时钟打开 注意我们这个代码还需要TIM2输出PWM所以输入捕获的定时器要换一个我们就换到TIM3这里在组建IC捕获模块TIM2是PWM已经定义好的捕获模块要重新定义一个。其次我们这里用到的是TIM3通道1查引脚定义表你就知道为什么连PA6。 第二步GPIO初始化把GPIO配置成输入模式一般选择上拉输入或者浮空输入模式 第三步配置时基单元让CNT计数器在内部时钟的驱动下自增运行这一步和之前的代码是一样的 ARR自动重装值根据之前的分析arr越大输入捕获越能更精准地测更小的频率其次防止计数溢出。 72M/预分频就是计数器自增的频率就是计数标准频率。这个需要根据你信号频率的分布范围来调整我暂时先给72-1这样标准频率就是72M/721MHz。 第四步配置输入捕获单元包括滤波器、极性、直连通道还是交叉通道、分频器这些参数用一个结构体就可以统一进行配置了 第五步选择从模式的触发源。触发源选择为TI1FP1这里调用一个库函数给一个参数就行了 第六步选择触发之后执行的操作。执行Reset操作这里也是调用一个库函数就行了 最后当这些电路都配置好之后调用TIM_Cmd函数开启定时器,这样所有的电路就能配合起来按照我们的要求工作了。直接读取CCR寄存器然后按照fc/NN是读取CCR的值计算一下就行了。这就是整个程序的思路 fc72M/(PSC1) main.c #include stm32f10x.h // Device header #include Delay.h #include OLED.h #include PWM_LED.h #include IC.hint main(void) {OLED_Init(); //初始化OLEDpwm_init();IC_init();//初始化整个电路 OLED_ShowString(1,1,Freq:00000Hz);//PA0口输出1khz频率50%占空比的待测信号;PWM模块将待测信号输出给PA0PA0然后通过导线输入到PA6PA6是TIM3的通道1通道1通过输入捕获模块测量得到频率然后在主循环里不断刷新显示频率PWM_setPSC(720-1); //频率72M/(psc1)/(arr1) //频率72M/720/100 1khzPWM_SetCompare1(50);//占空比ccr/ARR1 //占空比50/100 50%while(1){OLED_ShowNum(1,6,IC_GetFreq(),5);//不断刷新显示频率} }IC.c输入捕获 #include stm32f10x.h // Device headervoid IC_init(void) {//1.打开时钟选择内部时钟//使用APB1的开启时钟函数TIM3是APB1总线的外设RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//打开时钟,复用PA6 TIM3_CH1引脚图//2.初始化GPIOGPIO_InitTypeDef GPIO_InitStructure;//结构体变量名GPIO_InitStructureGPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU;//上拉输入GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;//默认50mhz输出GPIO_Init(GPIOA,GPIO_InitStructure);//使用的是地址传递//3.1.初始化时基单元//选择时基单元的时钟,选择内部时钟;若不调用这个函数系统上电也是默认是内部时钟TIM_InternalClockConfig(TIM3);//3.2.配置时基单元参数 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; //指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; //计数器模式向上计数/*公式 PWM频率Freq CK_PSC / (PSC 1) / (ARR 1)PWM占空比Duty CCR / (ARR 1)PWM分辨率Reso 1 / (ARR 1) */TIM_TimeBaseInitStructure.TIM_Period 65536 - 1; //ARR周期最好要设置大一些防止计数溢出16位的计数器可以满量程计数是65535TIM_TimeBaseInitStructure.TIM_Prescaler 72 - 1; //PSC预分频器标准频率就是72M/721MHz这个值决定了测周法的标准频率fc72M/预分频就是计数器自增的频率就是计数标准频率需要根据信号频率的分步范围来调整TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器的值TIM_TimeBaseInit(TIM3,TIM_TimeBaseInitStructure);//4.初始化输入捕获单元TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICInitStructure.TIM_Channel TIM_Channel_1;//选择通道使用TIM3的通道1TIM_ICInitStructure.TIM_ICFilter 0xF;//配置输入捕获的滤波器数越大滤波效果越好每个数值对应的采样频率和采样次数在参考手册里有若信号有毛刺和噪声就可以增大滤波器参数可以有效避免干扰 //注意滤波器和分频器的区别,虽然他俩都是计次的东西,但是滤波器计次,并不会改变信号的原有频率,一般滤波器的采样频率都会远高于信号频率,所以它只会滤除高频噪声,使信号更平滑 //1000Hz滤波之后仍然是1000Hz,信号频率不会变化,而分频器就只对信号本身进行计次自然会改变频率1000Hz二分频之后就是500HzTIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising;//对应边沿检测、极性选择部分可以选择上升沿触发/下降沿触发/上升沿和下降沿都触发TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1;//分频器触发信号分频器不分频就是每次触发都有效2分频就是每隔一次有效一次以此类推TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI;//选择触发信号从哪个引脚输入对应配置数据选择器的。可以选择直连通道/交叉通道/TRC引脚TIM_ICInit(TIM3,TIM_ICInitStructure);//5.配置触发源选择配置TRGI的触发源为TI1FP1TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);//触发源选择TI1FP1//6.配置从模式为ResetTIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);//从模式选择Reset//7.启动定时器调用TIM_CmdTIM_Cmd(TIM3,ENABLE);//CNT就会在内部时钟的驱动下不断自增即使没有信号过来它也会不断自增有信号来的时候CNT就会在从模式的作用下自动清零并不会影响测量/*初始化之后整个电路就能全自动测量了当我们想查看频率时需要读取CCR进行计算所以需要在下面写一个函数*/ }uint32_t IC_GetFreq(void) { //使用测周法的公式fc72M/psc1目前psc72-1所以fc1MHzreturn 1000000 / (TIM_GetCapture1(TIM3) 1); //返回的是最新一个周期的频率值单位是HZ 1MHz1000000 / N(就是读取CCR的值// 这里加1否则结果就如下图所示频率多1 }PWM.c #include stm32f10x.h // Device header/* pwm初始化函数基本步骤参考笔记PWM基本结构图 第一步RCC开启时钟把要用的TIM外设和GPIO外设的时钟打开 第二步配置单元包括时钟源选择和时基单元都配置好 第三步配置输出比较单元包括CCR值、输出比较模式、极性选择、输出使能这些参数在库函数里也是用结构体统一来配置 第四步配置GPIO把PWM对应的GPIO口初始化为复用推挽输出的配置Pwm和GPIO的对应关系可以参考引脚定义表 第五步运行控制启动计数器这样就能输出PWM了 *///PWM控制呼吸灯的代码逻辑是初始化TIM2的通道1产生一个PWM波形输出引脚是PA0然后通过PWM_SetCompare1可以调节CCR1寄存器的值从而控制PWM的占空比PWM的频率是固定写好在初始化程序里了运行时候调节不太方便 //在最后再加一个函数用来便捷地调节PWM频率PSC和ARR都可以调节频率但是调节ARR会影响占空比通过PSC调节频率不会影响占空比所以计划是固定ARR值通过调节PSC来改变PWM频率 //一般可以根据分辨率的要求先确定ARRPSC决定频率CCR决定占空比void pwm_init(void) {//1.打开时钟选择内部时钟//使用APB1的开启时钟函数TIM2是APB1总线的外设RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开时钟//2.初始化时基单元//选择时基单元的时钟,选择内部时钟;若不调用这个函数系统上电也是默认是内部时钟TIM_InternalClockConfig(TIM2);//3.配置时基单元 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; //指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; //计数器模式/*公式PWM频率Freq CK_PSC / (PSC 1) / (ARR 1)PWM占空比Duty CCR / (ARR 1)PWM分辨率Reso 1 / (ARR 1)*/TIM_TimeBaseInitStructure.TIM_Period 100 - 1; //ARR 周期TIM_TimeBaseInitStructure.TIM_Prescaler 720 - 1; //PSC 预分频器TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器的值TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2,TIM_FLAG_Update);//4.初始化输出比较单元通道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 50;//设置CCRPulse直译是脉冲TIM_OC1Init(TIM2, TIM_OCInitStructure);//使用PA0口对应是第一个输出比较通道在TIM2的OC1通道上就可以输出PWM波形了//5.初始化GPIOGPIO_InitTypeDef GPIO_InitStructure; //结构体变量名GPIO_InitStructureGPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //复用推挽输出PWM波形通过引脚输出使用定时器来控制引脚输出数据寄存器将被断开输出控制权将转移给片上外设这里片上外设引脚连接的就是TIM2的CH1通道GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; //默认50mhz输出GPIO_Init(GPIOA,GPIO_InitStructure); //使用的是地址传递 //6.启动定时器TIM_Cmd(TIM2,ENABLE);//PWM波形就能通过PA0输出了 }//在运行过程更改CCR使用函数TIM_SetCompare1封装用来单独更改通道1的CCR值进而改变[占空比] void PWM_SetCompare1(uint16_t Compare1)//TIM_SetCompare1封装 {TIM_SetCompare1(TIM2,Compare1); }//封装此函数在初始化之后单独修改PSC,进而改变[频率] void PWM_setPSC(uint16_t prescaler) { //调用库函数里单独写入PSC的函数在tim.h中找这个函数还有一个重装模式的参数所以叫TIM_PrescalerConfigTIM_PrescalerConfig(TIM2,prescaler,TIM_PSCReloadMode_Immediate);//写入PSC,第二个参数是写入PSC的值直接将外层函数的prescaler参数传进去第三个参数是重装模式还是影子寄存器、预装载这个问题就是写入的值是立刻生效还是在更新事件生效立刻生效可能会在值改变时产生切断波形的现象会出现不完整的周期更新事件生效就是会有一个缓存器延迟参数的写入时间等一个周期结束了在更新事件时再统一改变参数保证每个周期的完整// 这里选立刻生效}PWMI模式测频率占空比 在输入捕获模式测频率做修改 需要将输入捕获初始化的部分进行一下升级配置成两个通道同时捕获同一个引脚的模式怎么配置呢 两种方法 第一种把这个通道初始化的部分复制一份这个结构体定义的不要复制了。然后呢通道1是直连输入上升沿触发沿用这个配置。接着下面通道1改成通道2直连输入改成交叉输入上升沿触发改成下降沿触发这样看一下是不是就对应我们PPT的这个结构了。通道1直连输入上升沿触发通道2交叉输入下降沿触发这样就可以了。 第二种ST公司怕我们麻烦库里专门封装了函数快捷完成配置。只针对于通道1和通道2 写一个获取占空比的函数根据上一小节的分析高电平的计数值存在CCR2里整个周期的计数值存在CCR1里我们用CCR2/CCR1就能得到占空比了 CCR总少1应该是CCR从0开始计数的 main.c #include stm32f10x.h // Device header #include Delay.h #include OLED.h #include PWM_LED.h #include IC.hint main(void) {OLED_Init(); //初始化OLEDpwm_init();IC_init();//初始化整个电路 OLED_ShowString(1,1,Freq:00000Hz);OLED_ShowString(2,1,Duty:00%);//PA0口输出1khz频率50%占空比的待测信号;PWM模块将待测信号输出给PA0PA0然后通过导线输入到PA6PA6是TIM3的通道1通道1通过输入捕获模块测量得到频率然后在主循环里不断刷新显示频率PWM_setPSC(7200-1); //频率72M/(psc1)/(arr1) //频率72M/720/100 1khzPWM_SetCompare1(80);//占空比ccr/ARR1 //占空比50/100 50%while(1){OLED_ShowNum(1,6,IC_GetFreq(),5);//不断刷新显示频率OLED_ShowNum(2,6,IC_GetDuty(),2);//不断刷新显示占空比} } IC.c #include stm32f10x.h // Device header//PWMI模式方法1修改上一个程序的4.初始化输入捕获单元 //方法2使用TIM_PWMIConfig函数void IC_init(void) {//1.打开时钟选择内部时钟//使用APB1的开启时钟函数TIM3是APB1总线的外设RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//打开时钟,PA6的通道1//2.初始化GPIOGPIO_InitTypeDef GPIO_InitStructure;//结构体变量名GPIO_InitStructureGPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU;//上拉输入GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;//默认50mhz输出GPIO_Init(GPIOA,GPIO_InitStructure);//使用的是地址传递//3.1.初始化时基单元//选择时基单元的时钟,选择内部时钟;若不调用这个函数系统上电也是默认是内部时钟TIM_InternalClockConfig(TIM3);//3.2.配置时基单元参数 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; //指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; //计数器模式向上计数/*公式 PWM频率Freq CK_PSC / (PSC 1) / (ARR 1)PWM占空比Duty CCR / (ARR 1)PWM分辨率Reso 1 / (ARR 1) 目前我们给的标准频率时1mhz计数器最大只能计到65535所以所测量的最低频率是1m/6553515hz如果信号频率再低计数器就要溢出了所以最低频率就是15hz左右如果想再降低一些最低频率的限制可以把psc再加大点这样标准频率就更低所支持测量的最低频率也就更低最大频率没有界限1MHZ信号频率接近1mhz时误差已经非常大了*/TIM_TimeBaseInitStructure.TIM_Period 65536 - 1; //ARR周期最好要设置大一些防止计数溢出16位的计数器可以满量程计数是65535TIM_TimeBaseInitStructure.TIM_Prescaler 72 - 1; //PSC预分频器标准频率就是72M/721MHz这个值决定了测周法的标准频率fc72M/预分频就是计数器自增的频率就是计数标准频率需要根据信号频率的分步范围来调整TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器的值TIM_TimeBaseInit(TIM3,TIM_TimeBaseInitStructure);//4.初始化输入捕获单元PWMI模式需配置成两个通道同时捕获同一个引脚的模式一个简单的想法是将通道初始化部分复制一份结构体定义不需复制通道1是直连模式上升沿触发通道2也延用这个配置TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICInitStructure.TIM_Channel TIM_Channel_1;//选择通道使用TIM3的通道1TIM_ICInitStructure.TIM_ICFilter 0xF;//配置输入捕获的滤波器数越大滤波效果越好每个数值对应的采样频率和采样次数在参考手册里有若信号有毛刺和噪声就可以增大滤波器参数可以有效避免干扰TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising;//对应边沿检测、极性选择部分可以选择上升沿触发/下降沿触发/上升沿和下降沿都触发TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1;//分频器触发信号分频器不分频就是每次触发都有效2分频就是每隔一次有效一次以此类推TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI;//选择触发信号从哪个引脚输入对应配置数据选择器的。可以选择直连通道/交叉通道/TRC引脚TIM_ICInit(TIM3,TIM_ICInitStructure);//方法1将通道初始化部分复制一份结构体定义不需复制 // TIM_ICInitStructure.TIM_Channel TIM_Channel_2;//改为通道2 // TIM_ICInitStructure.TIM_ICFilter 0xF; // TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Falling;//改为下降沿触发 // TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; // TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_IndirectTI;//交叉输入 // TIM_ICInit(TIM3,TIM_ICInitStructure);//方法2使用TIM_PWMIConfig函数,可快捷地把电路配置成PWMI模式的标准结构这个函数只支持通道1和2不支持通道3和4和方法1的效果是一样的只需传入一个通道的参数就行了在函数里会自动把剩下的一个通道初始化成相反的配置比如已经传入了通道1、直连、上升沿那函数里就会顺带配置通道2、交叉、下降沿TIM_PWMIConfig(TIM3,TIM_ICInitStructure);//5.配置触发源选择配置TRGI的触发源为TI1FP1TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);//触发源选择TI1FP1//6.配置从模式为ResetTIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);//从模式选择Reset//7.启动定时器调用TIM_CmdTIM_Cmd(TIM3,ENABLE);//CNT就会在内部时钟的驱动下不断自增即使没有信号过来它也会不断自增有信号来的时候CNT就会在从模式的作用下自动清零并不会影响测量/*初始化之后整个电路就能全自动测量了当我们想查看频率时需要读取CCR进行计算所以需要在下面写一个函数*/ }//获取频率的函数 uint32_t IC_GetFreq(void) { //使用测周法的公式fc72M/psc1目前psc72-1所以fc1MHzreturn 1000000 / (TIM_GetCapture1(TIM3) 1); //返回的是最新一个周期的频率值单位是HZ 1MHz1000000 / N(就是读取CCR的值 }//获取占空比的函数 uint32_t IC_GetDuty(void) { //高电平的计数值存在CCR2里整个周期的计数值存在CCR1里用CCR2/CCR1就能得到占空比了return (TIM_GetCapture2(TIM3) 1) * 100 / (TIM_GetCapture1(TIM3) 1);//显示整数的话给它乘100这样返回值的范围就是0-100对应占空比0%-100%//经过实测CCR总会少一个数所以需要各加一个1补回来}pwm.c #include stm32f10x.h // Device header/* pwm初始化函数基本步骤参考笔记PWM基本结构图 第一步RCC开启时钟把要用的TIM外设和GPIO外设的时钟打开 第二步配置单元包括时钟源选择和时基单元都配置好 第三步配置输出比较单元包括CCR值、输出比较模式、极性选择、输出使能这些参数在库函数里也是用结构体统一来配置 第四步配置GPIO把PWM对应的GPIO口初始化为复用推挽输出的配置Pwm和GPIO的对应关系可以参考引脚定义表 第五步运行控制启动计数器这样就能输出PWM了 *///PWM控制呼吸灯的代码逻辑是初始化TIM2的通道1产生一个PWM波形输出引脚是PA0然后通过PWM_SetCompare1可以调节CCR1寄存器的值从而控制PWM的占空比PWM的频率是固定写好在初始化程序里了运行时候调节不太方便 //在最后再加一个函数用来便捷地调节PWM频率PSC和ARR都可以调节频率但是调节ARR会影响占空比通过PSC调节频率不会影响占空比所以计划是固定ARR值通过调节PSC来改变PWM频率 //一般可以根据分辨率的要求先确定ARRPSC决定频率CCR决定占空比void pwm_init(void) {//1.打开时钟选择内部时钟//使用APB1的开启时钟函数TIM2是APB1总线的外设RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开时钟//2.初始化时基单元//选择时基单元的时钟,选择内部时钟;若不调用这个函数系统上电也是默认是内部时钟TIM_InternalClockConfig(TIM2);//3.配置时基单元 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; //指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; //计数器模式/*公式PWM频率Freq CK_PSC / (PSC 1) / (ARR 1)PWM占空比Duty CCR / (ARR 1)PWM分辨率Reso 1 / (ARR 1)*/TIM_TimeBaseInitStructure.TIM_Period 100 - 1; //ARR 周期TIM_TimeBaseInitStructure.TIM_Prescaler 720 - 1; //PSC 预分频器TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器的值TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2,TIM_FLAG_Update);//4.初始化输出比较单元通道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 50;//设置CCRPulse直译是脉冲TIM_OC1Init(TIM2, TIM_OCInitStructure);//使用PA0口对应是第一个输出比较通道在TIM2的OC1通道上就可以输出PWM波形了//5.初始化GPIOGPIO_InitTypeDef GPIO_InitStructure; //结构体变量名GPIO_InitStructureGPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //复用推挽输出PWM波形通过引脚输出使用定时器来控制引脚输出数据寄存器将被断开输出控制权将转移给片上外设这里片上外设引脚连接的就是TIM2的CH1通道GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; //默认50mhz输出GPIO_Init(GPIOA,GPIO_InitStructure); //使用的是地址传递 //6.启动定时器TIM_Cmd(TIM2,ENABLE);//PWM波形就能通过PA0输出了 }//在运行过程更改CCR使用函数TIM_SetCompare1封装用来单独更改通道1的CCR值进而改变占空比 void PWM_SetCompare1(uint16_t Compare1)//TIM_SetCompare1封装 {TIM_SetCompare1(TIM2,Compare1); }//封装此函数在初始化之后单独修改PSC,进而改变频率 void PWM_setPSC(uint16_t prescaler) { //调用库函数里单独写入PSC的函数在tim.h中找这个函数还有一个重装模式的参数所以叫TIM_PrescalerConfigTIM_PrescalerConfig(TIM2,prescaler,TIM_PSCReloadMode_Immediate);//写入PSC,第二个参数是写入PSC的值直接将外层函数的prescaler参数传进去第三个参数是重装模式还是影子寄存器、预装载这个问题就是写入的值是立刻生效还是在更新事件生效立刻生效可能会在值改变时产生切断波形的现象会出现不完整的周期更新事件生效就是会有一个缓存器延迟参数的写入时间等一个周期结束了在更新事件时再统一改变参数保证每个周期的完整}最后我们来研究一下这个测频率的性能。 首先是测频率的范围目前我们给的标准频率是1MHz计数器最大只能计到65535。所以所测量的最低频率是1M/65535这个值算一下大概是15Hz。如果信号频率再低计数器就要溢出了所以最低频率就是15Hz左右。那如果想要再降低一些最低频率的限制我们可以把这个预分频再加大点这样标准频率就更低所支持测量的最低频率也就更低。这是测量频率的下限。 测得的频率等于fc/N这里的N值就是CNT里面过去的当N越大频率越小但是CNT最大不能超过ARR的值(最大为65535)所以测量的最小频率大概是15Hz。 然后是测量的上限就是支持的最大频率。这个最大频率并没有一个明显的界限因为随着待测频率的增大误差也会逐渐增大如果非要找个频率上限那应该就是标准频率1MHZ超过1MHz信号频率比标准频率还高那肯定测不了了。但是这个1MHz的上限并没有意义因为信号频率接近1MHz时误差已经非常大了所以最大频率要看你对误差的要求。上一小节我们说到了正负1误差计100个数误差1个相对误差就是百分之一计1000个数误差1个相对误差就是千分之一所以正负1误差可以认为是1/计数值。在这里如果要求误差等于千分之一时频率为上限那这个上限就是1M/10001KHz如果要求误差可以到百分之一那频率上限就是1M/10010KHz这就是频率的上限如果想提高频率的上限那我们在这里时基单元初始化时就要把PSC给降低一点.提高标准频率上限就会提高。除此之外如果频率还要更高那我们就要考虑一下测频法了。测频法适合高频测周法适合低频我们这里是测周法所以对于非常高的频率还是交给测频法来解决吧。 然后呢还有一个就是误差分析。除了我们之前说的正负1误差外在实际测量的时候还会有晶振误差。比如我们STM32的晶振不是那么准在计次几百几万次之后误差累积起来也会造成一些影响。 当然目前我们这个现象还是自己测量自己不存在晶振误差所以数值还是非常稳定的如果你要测量别的信号那数值可能就会有些抖动了后期可以再做一些滤波处理。 TIM编码器接口 实验现象 现象接了一个旋转编码器模块这个代码和之前我们写的旋转编码器计次的代码实现的功能基本都是一样的。目前我们这个代码本质上也是旋转编码器计次只不过这个代码是通过定时器的编码器接口来自动计次。而我们之前的代码是通过触发外部中断然后在中断函数里手动进行计次使用编码器接口的好处就是节约软件资源。 如果使用外部中断来计次那当电机高速旋转时编码器每秒产生成千上万个脉冲程序就得频繁进中断然后进中断之后完成的任务又只是简单的加—减一是不是我们的软件资源就被这种简单而又低级的工作给占用了。所以对于这种需要频繁执行操作又比较简单的任务一般我们都会设计一个硬件电路模块来自动完成。那我们本节这个编码器接口就是用来自动给编码器进行计次的电路。如果我们每隔一段时间取一下计次值就能得到编码器旋转的速度了。 我们旋转试一下向右慢速旋转数值为正计次比较小向右快速旋转计次就会增大然后向左慢速旋转数值为负计次比较小向左快速旋转计次也是负向增大然后停下来速度就是零这就是编码器测速的实验现象使用定时器的编码器接口啊再配合编码器就可以测量旋转速度和旋转方向了哈这里编码器测速一般应用在电机控制的项目上使用pwm驱动电机再使用编码器测量电机的速度然后再用PID算法进行闭环控制这是一个比较常见的使用场景哈一般电机旋转速度比较高会使用无接触式的霍尔传感器或者光栅进行测速哈我们这里为了方便就是用这个触点式的旋钮编码器来演示电机旋转呢就用人工旋转来模拟当然实际使用的话这个旋钮编码器和电机的霍尔光栅编码器都是一样的效果。 什么是正交编码器呢等会儿我们会介绍就是像这样输出的两个方波信号相位相差90度超前90度或者滞后90度分别代表正转和反转这就是正交编码器。 最终的实验现象就是一个编码器它有两个输出一个是A相一个是B相然后接入到STM32定时器的编码器接口编码器接口自动控制定时器时基单元中的CNT计数器进行自增或自减比如初始化之后CNT初始值为零然后编码器右转CNT就右转产生一个脉冲CNT就加一次比如右转产生十个脉冲后停下来那么这个过程CNT就由0自增到10停下来编码器左转CNT就–左转产生一个脉冲CNT减一次比如我编码器在左转产生五个脉冲那CNT就在原来十的基础上自减五停下来 这个编码器接口啊其实就是相当于一个带有方向控制的外部时钟它同时控制着cnt的计数时钟和计数方向这样的话CNT的值就表示了编码器的位置如果我们每隔一段时间取一次CNT的值再把CNT清零是不是每次取出来的值就表示了编码器的速度哈借用一下上一小节测频法和测周法的知识点这个编码器测速实际上就是测频法测正交脉冲的频率啊然后每隔一段时间取一次计次这就是测评法的思路只不过这个编码器接口计时更高级它能根据旋转方向不仅能自增计次还能自减计次是一个带方向的测速以上就是编码器接口的工作流程了。 每个高级定时器和通用定时器都拥有一个编码器接口这个编码器接口的资源还是比较紧张的如果一个定时器配置成了编码器接口模式那它基本上就干不了其他活了我们这个C8T6芯片只有TIM1、2、3、4四个定时器所以最多只能接四个编码器而且接完四个编码器就没有定时器可以用了所以如果你编码器比较多的话需要考虑一下这个资源够不够用不过实在不行的话你还是可以用外部中断来接编码器的这样就是用软件资源来弥补硬件资源了哈所以这里也可以看出硬件资源和软件资源是互补的硬件资源越多软件就会越轻松硬件不够呢那就软件来凑比如pwm我可以直接来个定时中断然后在中断里手动计数手动翻转电平比如输入捕获我可以来个外部中断然后在中断里手动把CNT取出来放在变量里比如编码器接口我也可以来外部中断然后在中断里手动自增或自减计数这都可以实现功能怎么输出比较啊输入捕获啊编码器接口啊都不需要 但是这样就是消耗软件资源了所以一般有硬件资源的情况下我们可以优先使用硬件资源这样节约下来的软件资源可以去干更重要的事情。 编码器接口的两个输入引脚借用了输入捕获的通道一和通道二这个等会从结构框图就可以看出来 编码器的两个输入引脚就是每个定时器的CH1和CH2引脚CH3和CH4不能接编码器这些就是编码器接口的简介了。 我们接下来来看一下正交编码器 正交编码器一般可以测量位置或者带有方向的速度值哈它一般有两个信号输出引脚一个是A相一个是B相编码器的样子和结构呢我们之前外部中断这里也介绍过哈我们大概回顾一下这里看 一下图片 第一个是只有一个光栅加红外对管的编码器这只能输出一个方波信号并不是正交编码器啊第二个图就是我们套件使用的编码器里面靠两个金属触点交替导通可以输出A相和B相两个正交信号是正交编码器啊这里有四个硬件上面两个是供电的正极和负极下面两个是A相和B相的输出第三个图是电机后面自带的一个编码器电机旋转带动中间的磁铁旋转两个霍尔传感器90度放置最终输出A相和B相两个正交信号是正交编码器下面这里一般有六根线最左和最右是直接接到电机的 然后是靠里一些的两根线是编码器电源最中间的两根就是A相和B相的输出了接着最后一个图是单独的编码器软件一般都是正交编码器当然也有的不是这个要看清商品的说明接线的引脚一般有六个两个是编码器电源两个是A相和B相一般还有一个编码器零位置的输出也就是Z相零位置就是编码器每转到一个固定位置时输出一个脉冲一般应用于位置常量哈校准零位置用的最后还有一个硬件一般是空引脚没有用到这就是常见的编码器外观和工作原理。 回到这里当编码器的旋转轴转起来时A相和B相就会输出这样的方波信号转得越快这个方波的频率就越高所以方波的频率就代表了速度我们取出任意一相的信号来测频率就能知道旋转速度了但是只有一相的信号无法测量旋转方向因为无论正转还是反转它都是这样的方波想要测量方向还必须要有另一根线的辅助比如我可以不要这个B相再定义一个方向输出角正转置高电平 反转置低电平这是一种解决方案哈但是这样的信号并不是正交信号另一种解决方案就是我们本节所说的正交信号当正转时A相提前B相90度反转时A相滞后B相90度当然这个正转是A相提前还是A相滞后并不是绝对的这只是一个极性问题毕竟正转和反转的定义也是相对的总之就是朝一个方向转是A相提前另一个方向是A相滞后。 那使用正交信号相比较单独定义一个方向引脚有什么好处呢 首先就是正交信号精度更高因为A、B相都可以计次相当于计次频率提高了一倍其次就是正交信号可以抗噪声因为正交信号两个信号必须是交替跳变的所以可以设计一个抗噪声电路。如果一个信号不变另一个信号连续跳变也就是产生了噪声那这时计次值是不会变化的。 看一下正交信号如何计次和区分旋转方向呢首先我们观察一下波形的特点在正转的时候第一个时刻A相上升沿对应B相此时是低电平也就是表里的第一行第二个时刻B相上升沿对应A相高电平是表里的第三行第三个时刻A相下降沿对应B相高电平是表里的第二行最后是B相下降沿对应A相低电平是表里的第四行再然后和第一个状态重复啊所以在正转的时候我们总结了右边这个表当出现这些边沿时对应另一项的状态是这四种反转同理。 所以我们编码器接口的设计逻辑就是首先把A相和B相的所有边沿作为计数器的计数时钟出现边沿信号时就计数自增或自减然后到底是增还是减呢这个计数的方向由另一相的状态来确定。当出现某个边沿时我们判断另一相的高低电平如果对应另一相的状态出现在上面这个表里那就是正转计数自增反之另一相的状态出现在下面这个表里那就是反转计数自减这样就能实现编码器接口的功能了这也是我们STM32定时器编码器接口的执行逻辑。 接下来我们就来看一下这个定时器的框图看一下这个编码器接口的电路是如何设计的。 编码器接口处于定时器的这个位置高级定时器和通用定时器都是一样的每个定时器都只有一个编码器接口然后基本定时器是没有编码器接口的我们来看一下这里编码器接口有两个输入端分别要接到编码器的A相和B相然后这里是两个网络编号分别写的是TI1FP1和TI2FP2对应的就是下面TI1FP1另一个是TI2FP2可以看出这个编码器接口的两个引脚借用了输入捕获单元的前两个通道所以最终编码器的输入引脚就是定时器的CH1和CH2这两个引脚信号的通路是CH1通过这里通向编码器接口CH2通过这里通向编码器接口CH3和CH4与编码器接口无关其中CH1和CH2的输入捕获滤波器和边缘检测编码器接口也有使用哈但是后面的是否交叉预分频器和CCR寄存器与编码器接口无关这就是编码器接口的输入部分那编码器接口的输出部分啊其实就相当于从模式控制器了去控制cnt的计数时钟和计数方向简单来说这里的输出执行流程是按照我们之前总结的那个表如果出现了边沿信号并且对应另一相的状态为正转则控制CNT自增否则控制CNT自减 注意在这里我们之前一直在使用的72MHZ的内部时钟CK_PSC 和我们在时机单元初始化时设置的计数方向并不会使用因为此时计数时钟和计数方向都处于编码器接口托管的状态计数器的自增和自减受编码器控制这就是编码器接口的电路结构了。 然后我们看一下这里我给出的一个编码器接口基本结构。 这个结构就比较清晰了输入捕获的前两个通道通过GPIO口接入编码器的A、B相然后通过滤波器和边沿检测极性选择 产生TI1FP1和TI2FP2通向编码器接口。编码器接口通过预分频器控制CNT计数器的时钟同时编码器接口还根据编码器的旋转方向控制CNT的计数方向编码器正转时CNT自增编码器反转时CNT自减。 另外这里ARR也是有效的一般我们会设置ARR为65535最大量程这样的话利用补码的特性很容易得到负数。比如CNT初始为0我正转CNT自增0、 1、2、3、4、5、6、7等等都没问题但是我反转呢CNT自减0下一个数就是65535接着是65534、65533等等这里负数不应该是-1、-2吗65535是不是就出问题了。但是没关系直接把这个16位的无符号数转换为16位的有符号数。根据补码的定义这个65535就对应-165534就对应-2有符号编码时负数按补码计算2^16 的补码 -1等等这样就可以直接得到负数非常方便这就是我们读出数据得到负数的一个小技巧。 最后我们来看一些工作细节和两个小例子。 那先看一下这里工作模式的这个表这个表描述的就是我们刚才说的编码器接口的工作逻辑哈这里TI1FP1和TI2FP2接的就是编码器的A、B相在A相和B相的上升沿或者下降沿触发计数到底是向上计数还是向下计数取决于边沿信号发生的这个时刻另一项的电平状态也就是这里的相对信号的电平TI1FP1对应TI2FP2TI2FP2对应TI1FP1就是另一相电平的意思然后在这里这个编码器还分了三种工作模式分别是仅在TI1计数、仅在TI2计数、和T都计数这三个模式是啥意思呢我们回到这里看一下我们目前总结的是这四种状态都是正转都可以计次自增下面这四种状态都是反转都可以计次自减这四种状态涉及了两个引脚分别是A相上升沿、A相下降沿B相上升沿、B相下降沿如果这四种状态都执行自增和自减就是A相和B相的边缘都记住那就对应这里的第三种模式TI1和TI2都计数当然这里我们还可以忽略一些边缘啊比如我们可以仅在A相的上升沿和下降沿自增或自减而B相的这两个状态忽略掉不执行计数或者仅在B相的上升沿和下降沿计数 A相的边缘不管它这样是不是也可以实现功能啊只不过是计次的精度低了些 总结一句话就是正转的计数都向上计数反转的计数都向下计数这是编码器接口执行的逻辑然后上面这两种模式应该也好理解就是指在一相的边缘技术另一项的边沿忽略和最下面这个模式都是一样的一般情况下我们都会使用最下面这个模式因为这个模式技术精度最高上面这两个模式如果你有这个需求的话可以了解一下这就是编码器接口的三种工作模式。 那接着我们再来看一下这个实例图这个图里使用的就是两个引脚的边沿都计数的模式执行的逻辑我放在这上面了然后看一下这里是TI1和TI2的时序信号下面是计数器值的变化情况第一个状态 TI1上升沿TI2低电平查表对应向上技术所以这里计数器变高了一级接着后面的这几个状态大家可以查一下表都是向上计数这是正转没问题接着后面这个地方展示的就是正交编码器抗噪声的原理了在这里TI2没有变化但是TI1却跳变了好几次这不符合正交编码器的信号规律正交信号两个输出交替变化就像人走路一样先迈左腿再迈右腿在左腿在右腿这里就是右腿没动左腿连续走了好几步显然这个左腿的动作是一个毛刺信号而通过我们上面这个表的逻辑就可以把这种一个腿没动另一个腿连走好几步的噪声滤掉。 然后继续往右看这里是反转的波形对照上表都查一下都是向下计数这里计数值就下降然后TI1不动TI2多次跳变计数值也是来回摆动过滤噪声啊最后是正转向上计数这就是编码器接口的一个实例。 最后我们再来看一下这个实例这个实例展示的是极性的变化对计数的影响前面是两个引脚都不反向的图后面这个是TI1反向TI2不反向的图。 TI1反相是什么意思呢 此时看下这个图这里TI1和TI2进来都会经过这个极性选择的部分。 在输入捕获模式下这个极性选择是选择上升有效还是下降沿有效的。但是根据我们刚才的分析编码器接口显然始终都是上升沿和下降沿都有效的上升沿和下降沿都需要计次所以在编码器接口模式下这里就不再是边沿的极性选择了而是高低电平的极性选择。如果我们选择上升沿的参数就是信号直通过来高低电平极性不反转如果选择下降沿的参数就是信号通过一个非门过来高低电平极性反转所以这里就会有两个控制极性的参数选择要不要在这里加一个非门反转一下极性。 显然这两个实例图的计数方向是相反的这有什么作用呢 比如你接一个编码器发现它数据的加减方向反了你想要正转的方向结果它自减了你想要反转的方向结果它自增了这时就可以调整一下极性把任意一个引脚反相就能反转计数方向了。当然如果想改变计数方向的话我们还可以直接把A、B相两个引脚换一下。 手册 示例代码编码器接口 这个接线和支线外部中断那一节的类似哈我们把旋转编码器插在左边VCC和GND接上电源正负极 下面的A相输出我们接到PA6引脚B相输出接到PA7引脚这里PA6和PA7引脚可以交换一下就是正转和反转的急性不一样而已但是PA6和PA7这两个引脚不能随便更换看一下引脚定义PA6和PA7是TIM3的通道1和通道2我们计划用TIM3接编码器所以需要接在PA6和PA7这两个引脚。 第一步RCC开启时钟开启GPIO和定时器的时钟 第二步配置GPIO这里需要把PA6和PA7配置成输入模式 第三步配置时基单元这里预分频器我们一般选择不分频自动重装一般给最大65535只需要个CNT执行计数就行了 第四步配置输入捕获单元。不过这里输入捕获单元只有滤波器和极性这两个参数有用后面的参数没有用到与编码器无关 第五步配置编码器接口模式。这个直接调用一个库函数就可以了 最后调用TIM_Cmd启动定时器就完事了 电路初始化完成之后CNT就会随着编码器旋转而自增自减如果想要测量编码器的位置那直接读出CNT的值就行了如果想测量编码器的速度和方向那就需要每隔一段固定的闸门时间取出一次CNT然后再把CNT清零这样就是测频法测量速度了。 main.c 这段代码主要作用是通过定时器定时执行 TIM2_IRQHandler 中断服务程序在定时器中断处理函数中读取编码器的值并将其存储在 speed 变量中然后在主循环中利用 OLED 显示器显示速度值。 值得注意的是在 main 函数中需要调用 OLED_Init() 进行 OLED 显示器的初始化并在使用 OLED 显示的地方调用 OLED_ShowString 和 OLED_ShowSignedNum 函数进行显示。 除了这些代码片段外程序的其他部分例如 OLED_Init、Timer_Init、Encoder_init 等函数的定义以及头文件的包含内容以及关于定时器和编码器的配置也是非常重要的。 #include stm32f10x.h // Device header #include Delay.h #include OLED.h #include Timer.h #include Encoder.huint16_t speed;int main(void) {OLED_Init(); //初始化OLEDTimer_Init(); //初始化定时器Encoder_init();OLED_ShowString(1,1,speed:);while(1){OLED_ShowSignedNum(1,7,speed,5);//每隔一段时间读取一次用定时中断//Delay_ms(1000);//人手转的比较慢可以给个闸门时间1s如果电机飞速转闸门时间就给短一点提高速度刷新频率防止计数器溢出//最好不要在主循环加入过长的Delay这样会阻塞主循环的执行最好使用下面的中断方法}}//定时器2中断函数放在使用中断的main.c文件中在startup文件中定时中断每隔1s执行一次 void TIM2_IRQHandler(void) //当定时器产生更新中断时这个函数就会自动被执行 {//检查中断标志位if(TIM_GetITStatus(TIM2,TIM_IT_Update) SET){//执行相应的用户代码speed Encoder_Get(); //定时器每隔1s读取一下速度存在speed变量里TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位}}Encoder.c 这段代码是针对 STM32 微控制器的编码器初始化和测速函数的实现用于读取编码器的旋转位置和速度。以下是对代码的简要说明 Encoder_init 函数是编码器的初始化函数主要包括了以下几个步骤 开启时钟选择内部时钟以及初始化 GPIO 接口。配置时基单元 TIM3设置预分频器、计数器模式和周期等参数。配置输入捕获单元通道TIM_ICInit设置滤波器和极性。最后调用 TIM_EncoderInterfaceConfig 函数配置编码器接口。启动定时器 TIM3。 Encoder_Get 函数是用于测速的函数主要实现了在固定的时间间隔内读取一次计数器的值并清零计数器。函数返回了测量得到的编码器的变化值即速度信息。 #include stm32f10x.h // Device header//编码器旋转控制CNT自增自减 //编码器初始化函数编码器电路初始化后CNT就会随着编码器旋转而自增自减直接读出CNT值就能测量编码器的位置测量编码器的速度和方向就需要每隔一段固定的闸门时间取出一次CNT然后再把CNT清零这就是测频法测量速度了 /* 第一步RCC开启时钟开启GPIO和定时器的时钟 第二步配置GPIO需将PA6和PA7配置成输入模式 第三步配置时基单元预分频器一般选择不分频ARR一般给最大值655535只需要CNT执行计数就行了 第四步配置输入捕获单元这里只有滤波器和极性两个参数有用后面的参数没有用到与编码器无关 第五步配置编码器接口模式直接调用一个库函数 最后调用TIM_Cmd启动定时器 */void Encoder_init(void) {//1.打开时钟选择内部时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//2.初始化GPIOGPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU;/* GPIO模式可以选择上拉、下拉或者浮空上拉和下拉如何选择呢我们一般可以看一下接在这个引脚的外部模块输出的默认电平如果外部模块空闲默认输出高电平我们就选择上拉输入默认输入高电平如果外部模块默认输出低电平我们配置下拉输入默认输入低电平和外部模块保持默认状态一致防止默认电瓶打架这是上拉和下拉的选择原则一般默认高电平是习惯的状态所以一般上拉输入用的比较多然后如果你不确定外部模块输出的默认状态或者外部信号输出功率非常小这时就尽量选择浮空输入没有上拉电阻和下拉电阻影响外部信号缺点就是当引脚悬空时没有默认电平了输入就会受噪声干扰来回不断的跳变这就是三种输入模式的选择原则*/GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA,GPIO_InitStructure); //不需要初始化时基单元下面这个内部时钟因为编码器接口会托管时钟编码器接口就是一个带方向控制的外部时钟所以内部时钟就不用了//TIM_InternalClockConfig(TIM3);//3.配置时基单元 /*公式PWM 频 率Freq CK_PSC / (PSC 1) / (ARR 1)PWM占空比Duty CCR / (ARR 1)PWM分辨率Reso 1 / (ARR 1)*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; //指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; //计数器模式这个参数也是没有作用的计数方向也是被编码器接口托管的TIM_TimeBaseInitStructure.TIM_Period 65536 - 1; //ARR 周期 满量程计数这样计数的范围是最大的而且方便换算成负数TIM_TimeBaseInitStructure.TIM_Prescaler 1 - 1; //PSC 预分频器不分频编码器的时钟直接驱动计数器TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器的值TIM_TimeBaseInit(TIM3,TIM_TimeBaseInitStructure); //初始化TIM3//4.配置输入捕获单元通道,编码器接口只使用了通道1和2的滤波器和极性选择//首先定义结构体变量然后StructInit给结构体赋一个初始值再部分修改我们想要的参数调用ICInit配置一遍电路结构体变量的配置在调用ICInit函数之后就写入到硬件的寄存器了所以ICInit之后这个结构体我们可以换个值继续使用、不需要重新定义新的结构体TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICStructInit(TIM_ICInitStructure);//结构体初始化,防止结构体中出现不确定值可能造成问题最好用StructInit给结构体赋一个初始值TIM_ICInitStructure.TIM_Channel TIM_Channel_1; //通道1TIM_ICInitStructure.TIM_ICFilter 0xF; //滤波器为0xF//TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; //电平极性为上升沿这里的上升沿参数代表的是高低电平极性不反转(前面讲的)等会配置编码器接口的时候也有极性配置属于重复配置这个其实可以删掉这里的上升沿并不代表上升沿有效因为编码器接口始终都是上升沿、下降沿都有效TIM_ICInit(TIM3, TIM_ICInitStructure);TIM_ICInitStructure.TIM_Channel TIM_Channel_2; //通道2TIM_ICInitStructure.TIM_ICFilter 0xF; //滤波器为0xF//TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; //电平极性为上升沿这里的上升沿参数代表的是高低电平极性不反转等会配置编码器接口的时候也有极性配置(下面第5步)属于重复配置这个其实可以删掉这里的上升沿并不代表上升沿有效因为编码器接口始终都是上升沿、下降沿都有效TIM_ICInit(TIM3, TIM_ICInitStructure);//5.配置编码器接口只需调用一个函数就行了需保证TIM_EncoderInterfaceConfig在TIM_ICInit函数之后否则TIM_ICInit覆盖TIM_EncoderInterfaceConfig函数的配置TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);//选择Rising是通道不反相选择Falling是通道反相重复配置TIM_ICPolarity_Rising后面的参数会覆盖前面的参数配置//6.启动定时器TIM_Cmd(TIM3,ENABLE); }int16_t Encoder_Get(void)// 这里返回不要写成uint16_t需要支持返回带负数的 {//测速在固定的匝门时间读一次CNT然后把CNT清零int16_t temp;//因为要先读取CNT再清零所以要用temp缓存一下temp TIM_GetCounter(TIM3);//读取CNT 这个就是计次/距离TIM_SetCounter(TIM3,0);//CNT清零 return temp; }Encoder.h 这段代码是编码器模块的头文件 “Encoder.h” 的内容。它使用了条件编译指令防止头文件的重复包含。 在头文件中声明了两个函数的原型 Encoder_init编码器的初始化函数。Encoder_Get获取编码器的值位置或速度的函数。 同时使用了预处理指令 #ifndef、#define、#endif确保头文件只包含一次以避免重复定义的错误。 通过包含这个头文件其他源文件就可以使用 Encoder_init 和 Encoder_Get 函数进行编码器的初始化和获取编码器的值。 #ifndef __ENCODER_H #define __ENCODER_Hvoid Encoder_init(void); int16_t Encoder_Get(void);#endif
http://www.pierceye.com/news/92793/

相关文章:

  • 手表网站大全网络推广竞价
  • 中创高科官方网站wordpress 页面添加js
  • 平台网站怎么做专业网站设计公司排名
  • 网站开发公司 重庆百度seo标题优化软件
  • wordpress 导入主题网站建设优化广告流量
  • 网店装修网站房屋装修设计费一般多少
  • 微信公众平台制作网站设计网站页面要怎么切图
  • 个人网站 备案 类型小红书笔记推广
  • 门户网站怎么创建网页制作步骤php
  • 六师五家渠市建设局网站wordpress 定时发布插件
  • 深圳外贸网站外贸网站建设安阳网站建设服务
  • wdcp 网站建设做民宿推广都有哪些网站
  • 常见的网站开发环境网站建设的结构
  • 贵阳网站推广优化公司做网站的公司搞什么活动
  • 安徽平台网站建设制作坪山网站设计的公司
  • 如何做挂qq的网站2017网站建设
  • wordpress语言切换网站保定广告设计公司
  • 做网站需要走公司吗运行一个网站要多少钱
  • 怎样可以免费做网站wap网站软件
  • 织梦手机网站免费模板漳州城乡建设局网站
  • 厦门建设网站的公司php除了写网站吗
  • 如何做全网影视网站居然之家装修公司怎么样
  • 佛山网站建设公司哪家最好万能软文范例800字
  • 网站排名优化如何做wordpress 免费版广告
  • 拓客网站建设建易网官网
  • 网站目录链接怎么做的建网站pc版 (报价)
  • 北京网站制作业务如何开展做网站海报
  • 网站的设计方法有哪些互动网络游戏公司网站建设
  • 公司网站开发制作公司国内重大新闻2022
  • 搜索引擎排名网站北京到广州快递要几天